Merge branch 'beta' into 'main'
2025.19.1 See merge request fedeo/software!15
This commit is contained in:
8
app.vue
8
app.vue
@@ -1,12 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import * as Sentry from "@sentry/browser"
|
import * as Sentry from "@sentry/browser"
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const user = useSupabaseUser()
|
|
||||||
const route = useRoute()
|
|
||||||
const tenants = (await supabase.from("tenants").select()).data
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const viewport = useViewport()
|
|
||||||
|
|
||||||
/*watch(viewport.breakpoint, (newBreakpoint, oldBreakpoint) => {
|
/*watch(viewport.breakpoint, (newBreakpoint, oldBreakpoint) => {
|
||||||
console.log('Breakpoint updated:', oldBreakpoint, '->', newBreakpoint)
|
console.log('Breakpoint updated:', oldBreakpoint, '->', newBreakpoint)
|
||||||
@@ -70,7 +65,6 @@ useSeoMeta({
|
|||||||
<UNotifications :class="platform === 'mobile' ? ['mb-14'] : []"/>
|
<UNotifications :class="platform === 'mobile' ? ['mb-14'] : []"/>
|
||||||
<USlideovers />
|
<USlideovers />
|
||||||
<UModals/>
|
<UModals/>
|
||||||
<VitePwaManifest/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
74
components/ArchiveButton.vue
Normal file
74
components/ArchiveButton.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['confirmed'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
required:false
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
required:false
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required:false
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const {color,variant, type} = props
|
||||||
|
|
||||||
|
const dataStore = useDataStore()
|
||||||
|
const dataType = dataStore.dataTypes[type]
|
||||||
|
|
||||||
|
const showModal = ref(false)
|
||||||
|
|
||||||
|
const emitConfirm = () => {
|
||||||
|
showModal.value = false
|
||||||
|
emit('confirmed')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UButton
|
||||||
|
:color="color"
|
||||||
|
:variant="variant"
|
||||||
|
@click="showModal = true"
|
||||||
|
>
|
||||||
|
Archivieren
|
||||||
|
</UButton>
|
||||||
|
<UModal v-model="showModal">
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
<span class="text-md font-bold">Archivieren bestätigen</span>
|
||||||
|
</template>
|
||||||
|
Möchten Sie diese/-s/-n {{dataType.labelSingle}} wirklich archivieren?
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="text-right">
|
||||||
|
<UButtonGroup>
|
||||||
|
<UButton
|
||||||
|
variant="outline"
|
||||||
|
@click="showModal = false"
|
||||||
|
>
|
||||||
|
Abbrechen
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
@click="emitConfirm"
|
||||||
|
class="ml-2"
|
||||||
|
color="rose"
|
||||||
|
>
|
||||||
|
Archivieren
|
||||||
|
</UButton>
|
||||||
|
</UButtonGroup>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
</UModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -41,7 +41,6 @@ const emitConfirm = () => {
|
|||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<UButtonGroup>
|
<UButtonGroup>
|
||||||
<UButton
|
<UButton
|
||||||
color="rose"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@click="showModal = false"
|
@click="showModal = false"
|
||||||
>
|
>
|
||||||
@@ -50,8 +49,9 @@ const emitConfirm = () => {
|
|||||||
<UButton
|
<UButton
|
||||||
@click="emitConfirm"
|
@click="emitConfirm"
|
||||||
class="ml-2"
|
class="ml-2"
|
||||||
|
color="rose"
|
||||||
>
|
>
|
||||||
Bestätigen
|
Archivieren
|
||||||
</UButton>
|
</UButton>
|
||||||
</UButtonGroup>
|
</UButtonGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
@@ -71,13 +70,13 @@ const showFile = (file) => {
|
|||||||
aspect-ratio: 1 / 1.414;
|
aspect-ratio: 1 / 1.414;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 0.7em;
|
margin: 0.7em;
|
||||||
border: 1px solid lightgrey;
|
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
|
transition: box-shadow 0.2s ease; /* für smooth hover */
|
||||||
}
|
}
|
||||||
|
|
||||||
.documentListItem:hover {
|
.documentListItem:hover {
|
||||||
border: 1px solid #69c350;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); /* sanfter Shadow beim Hover */
|
||||||
}
|
}
|
||||||
|
|
||||||
.previewEmbed {
|
.previewEmbed {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -27,7 +26,7 @@ const filetypes = ref([])
|
|||||||
const documentboxes = ref([])
|
const documentboxes = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
const {data} = await supabase.from("folders").select().eq("tenant",useProfileStore().currentTenant)
|
const data = await useEntities("folders").select()
|
||||||
|
|
||||||
data.forEach(folder => {
|
data.forEach(folder => {
|
||||||
let name = folder.name
|
let name = folder.name
|
||||||
@@ -55,20 +54,12 @@ const setup = async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
filetypes.value = await useSupabaseSelect("filetags")
|
filetypes.value = await useEntities("filetags").select()
|
||||||
documentboxes.value = await useSupabaseSelect("documentboxes")
|
documentboxes.value = await useEntities("documentboxes").select()
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
|
|
||||||
//Functions
|
|
||||||
const openDocument = async () => {
|
|
||||||
//selectedDocument.value = doc
|
|
||||||
openShowModal.value = true
|
|
||||||
console.log("open")
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateDocument = async () => {
|
const updateDocument = async () => {
|
||||||
const {url, ...objData} = props.documentData
|
const {url, ...objData} = props.documentData
|
||||||
delete objData.url
|
delete objData.url
|
||||||
@@ -91,12 +82,7 @@ const updateDocument = async () => {
|
|||||||
|
|
||||||
console.log(objData)
|
console.log(objData)
|
||||||
|
|
||||||
|
const {data,error} = await useEntities("files").update(objData.id, objData)
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("files")
|
|
||||||
.update(objData)
|
|
||||||
.eq('id',objData.id)
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(error) {
|
if(error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -114,13 +100,6 @@ const archiveDocument = async () => {
|
|||||||
props.documentData.archived = true
|
props.documentData.archived = true
|
||||||
await updateDocument()
|
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()
|
modal.close()
|
||||||
emit("update")
|
emit("update")
|
||||||
}
|
}
|
||||||
@@ -139,19 +118,19 @@ const itemOptions = ref([])
|
|||||||
const idToAssign = ref(null)
|
const idToAssign = ref(null)
|
||||||
const getItemsBySelectedResource = async () => {
|
const getItemsBySelectedResource = async () => {
|
||||||
if(resourceToAssign.value === "project") {
|
if(resourceToAssign.value === "project") {
|
||||||
itemOptions.value = await useSupabaseSelect("projects")
|
itemOptions.value = await useEntities("projects").select()
|
||||||
} else if(resourceToAssign.value === "customer") {
|
} else if(resourceToAssign.value === "customer") {
|
||||||
itemOptions.value = await useSupabaseSelect("customers")
|
itemOptions.value = await useEntities("customers").select()
|
||||||
} else if(resourceToAssign.value === "vendor") {
|
} else if(resourceToAssign.value === "vendor") {
|
||||||
itemOptions.value = await useSupabaseSelect("vendors")
|
itemOptions.value = await useEntities("vendors").select()
|
||||||
} else if(resourceToAssign.value === "vehicle") {
|
} else if(resourceToAssign.value === "vehicle") {
|
||||||
itemOptions.value = await useSupabaseSelect("vehicles")
|
itemOptions.value = await useEntities("vehicles").select()
|
||||||
} else if(resourceToAssign.value === "product") {
|
} else if(resourceToAssign.value === "product") {
|
||||||
itemOptions.value = await useSupabaseSelect("products")
|
itemOptions.value = await useEntities("products").select()
|
||||||
} else if(resourceToAssign.value === "plant") {
|
} else if(resourceToAssign.value === "plant") {
|
||||||
itemOptions.value = await useSupabaseSelect("plants")
|
itemOptions.value = await useEntities("plants").select()
|
||||||
} else if(resourceToAssign.value === "contract") {
|
} else if(resourceToAssign.value === "contract") {
|
||||||
itemOptions.value = await useSupabaseSelect("contracts")
|
itemOptions.value = await useEntities("contracts").select()
|
||||||
} else {
|
} else {
|
||||||
itemOptions.value = []
|
itemOptions.value = []
|
||||||
}
|
}
|
||||||
@@ -165,20 +144,9 @@ const updateDocumentAssignment = async () => {
|
|||||||
|
|
||||||
const folderToMoveTo = ref(null)
|
const folderToMoveTo = ref(null)
|
||||||
const moveFile = async () => {
|
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) {
|
const res = await useEntities("files").update(props.documentData.id, {folder: folderToMoveTo.value})
|
||||||
console.log(error)
|
|
||||||
toast.add({title: "Fehler beim verschieben", color:"rose"})
|
|
||||||
} else {
|
|
||||||
toast.add({title: "Datei verschoben"})
|
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
modal.close()
|
modal.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,14 +171,11 @@ const moveFile = async () => {
|
|||||||
|
|
||||||
</template>
|
</template>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="w-1/3">
|
<div :class="useCapacitor().getIsNative() ? ['w-full'] : ['w-1/3']">
|
||||||
<object
|
<PDFViewer
|
||||||
class="bigPreview"
|
v-if="props.documentData.id && props.documentData.path.toLowerCase().includes('pdf')"
|
||||||
:data="`${props.documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
|
:file-id="props.documentData.id" />
|
||||||
type="application/pdf"
|
|
||||||
v-if="props.documentData.path.toLowerCase().includes('pdf')"
|
|
||||||
|
|
||||||
/>
|
|
||||||
<img
|
<img
|
||||||
class=" w-full"
|
class=" w-full"
|
||||||
:src="props.documentData.url"
|
:src="props.documentData.url"
|
||||||
@@ -218,21 +183,14 @@ const moveFile = async () => {
|
|||||||
v-else
|
v-else
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/3 p-5">
|
<div class="w-2/3 p-5" v-if="!useCapacitor().getIsNative()">
|
||||||
<UButtonGroup>
|
<UButtonGroup>
|
||||||
<ButtonWithConfirm
|
<ArchiveButton
|
||||||
color="rose"
|
color="rose"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
type="files"
|
||||||
@confirmed="archiveDocument"
|
@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
|
<UButton
|
||||||
:to="props.documentData.url"
|
:to="props.documentData.url"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const uploadInProgress = ref(false)
|
|||||||
const availableFiletypes = ref([])
|
const availableFiletypes = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
availableFiletypes.value = await useSupabaseSelect("filetags")
|
availableFiletypes.value = await useEntities("filetags").select()
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|||||||
@@ -48,17 +48,14 @@ defineShortcuts({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
|
|
||||||
|
|
||||||
const dataType = dataStore.dataTypes[type]
|
const dataType = dataStore.dataTypes[type]
|
||||||
const openTab = ref(0)
|
const openTab = ref(0)
|
||||||
|
|
||||||
const item = ref(JSON.parse(props.item))
|
const item = ref(JSON.parse(props.item))
|
||||||
console.log(item.value)
|
console.log(item.value)
|
||||||
|
|
||||||
@@ -152,11 +149,11 @@ const loadOptions = async () => {
|
|||||||
|
|
||||||
for await(const option of optionsToLoad) {
|
for await(const option of optionsToLoad) {
|
||||||
if(option.option === "countrys") {
|
if(option.option === "countrys") {
|
||||||
loadedOptions.value[option.option] = (await supabase.from("countrys").select()).data
|
loadedOptions.value[option.option] = useEntities("countrys").selectSpecial()
|
||||||
} else if(option.option === "units") {
|
} else if(option.option === "units") {
|
||||||
loadedOptions.value[option.option] = (await supabase.from("units").select()).data
|
loadedOptions.value[option.option] = useEntities("units").selectSpecial()
|
||||||
} else {
|
} else {
|
||||||
loadedOptions.value[option.option] = (await useSupabaseSelect(option.option))
|
loadedOptions.value[option.option] = (await useEntities(option.option).select())
|
||||||
|
|
||||||
if(dataType.templateColumns.find(x => x.key === option.key).selectDataTypeFilter){
|
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))
|
loadedOptions.value[option.option] = loadedOptions.value[option.option].filter(i => dataType.templateColumns.find(x => x.key === option.key).selectDataTypeFilter(i, item))
|
||||||
@@ -208,12 +205,14 @@ const createItem = async () => {
|
|||||||
let ret = null
|
let ret = null
|
||||||
|
|
||||||
if(props.inModal) {
|
if(props.inModal) {
|
||||||
ret = await dataStore.createNewItem(type,item.value,true)
|
ret = await useEntities(type).create(item.value, true)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ret = dataStore.createNewItem(type,item.value)
|
ret = await useEntities(type).create(item.value)//dataStore.createNewItem(type,item.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('returnData', ret)
|
emit('returnData', ret)
|
||||||
|
modal.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateItem = async () => {
|
const updateItem = async () => {
|
||||||
@@ -222,7 +221,8 @@ const updateItem = async () => {
|
|||||||
if(props.inModal) {
|
if(props.inModal) {
|
||||||
ret = await dataStore.updateItem(type,item.value, oldItem.value,true)
|
ret = await dataStore.updateItem(type,item.value, oldItem.value,true)
|
||||||
} else {
|
} else {
|
||||||
ret = await dataStore.updateItem(type,item.value, oldItem.value)
|
console.log(item.value)
|
||||||
|
ret = await useEntities(type).update(item.value.id, item.value)//await dataStore.updateItem(type,item.value, oldItem.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('returnData', ret)
|
emit('returnData', ret)
|
||||||
@@ -255,20 +255,13 @@ const updateItem = async () => {
|
|||||||
>{{item.id ? `${dataType.labelSingle} bearbeiten` : `${dataType.labelSingle} erstellen` }}</h1>
|
>{{item.id ? `${dataType.labelSingle} bearbeiten` : `${dataType.labelSingle} erstellen` }}</h1>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<ButtonWithConfirm
|
<ArchiveButton
|
||||||
v-if="platform !== 'mobile'"
|
|
||||||
color="rose"
|
color="rose"
|
||||||
|
v-if="platform !== 'mobile'"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@confirmed="dataStore.updateItem(type,{...item,archived: true}, oldItem)"
|
:type="type"
|
||||||
>
|
@confirmed="useEntities(type).archive(item.id)"
|
||||||
<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
|
<UButton
|
||||||
v-if="item.id"
|
v-if="item.id"
|
||||||
@click="updateItem"
|
@click="updateItem"
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
import {useTempStore} from "~/stores/temp.js";
|
import {useTempStore} from "~/stores/temp.js";
|
||||||
import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
|
import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
|
||||||
import EntityTable from "~/components/EntityTable.vue";
|
import EntityTable from "~/components/EntityTable.vue";
|
||||||
import EntityListMobile from "~/components/EntityListMobile.vue";
|
import EntityTableMobile from "~/components/EntityTableMobile.vue";
|
||||||
|
|
||||||
|
const { has } = usePermission()
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
||||||
@@ -15,9 +18,17 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
platform: {
|
platform: {
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
required: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(["sort"]);
|
||||||
|
|
||||||
|
|
||||||
const {type} = props
|
const {type} = props
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
@@ -74,7 +85,12 @@ const selectedFilters = ref(dataType.filters.filter(i => i.default).map(i => i.n
|
|||||||
|
|
||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
|
|
||||||
let tempItems = props.items
|
let tempItems = props.items.map(i => {
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
class: i.archived ? 'bg-red-500/50 dark:bg-red-400/50' : null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if(selectedFilters.value.length > 0) {
|
if(selectedFilters.value.length > 0) {
|
||||||
selectedFilters.value.forEach(filterName => {
|
selectedFilters.value.forEach(filterName => {
|
||||||
@@ -83,23 +99,6 @@ const filteredRows = computed(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
return useSearch(searchString.value, tempItems)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -139,13 +138,14 @@ const filteredRows = computed(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UButton
|
<UButton
|
||||||
v-if="platform !== 'mobile' && useRole().checkRight(`${type}-create`)"
|
v-if="platform !== 'mobile' && has(`${type}-create`)/*&& useRole().checkRight(`${type}-create`)*/"
|
||||||
@click="router.push(`/standardEntity/${type}/create`)"
|
@click="router.push(`/standardEntity/${type}/create`)"
|
||||||
class="ml-3"
|
class="ml-3"
|
||||||
>+ {{dataType.labelSingle}}</UButton>
|
>+ {{dataType.labelSingle}}</UButton>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
|
|
||||||
|
|
||||||
<UDashboardToolbar>
|
<UDashboardToolbar>
|
||||||
<template #left v-if="$slots['left-toolbar']">
|
<template #left v-if="$slots['left-toolbar']">
|
||||||
<slot name="left-toolbar"/>
|
<slot name="left-toolbar"/>
|
||||||
@@ -182,7 +182,7 @@ const filteredRows = computed(() => {
|
|||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardToolbar>
|
</UDashboardToolbar>
|
||||||
<EntityListMobile
|
<EntityTableMobile
|
||||||
v-if="platform === 'mobile'"
|
v-if="platform === 'mobile'"
|
||||||
:type="props.type"
|
:type="props.type"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
@@ -190,9 +190,11 @@ const filteredRows = computed(() => {
|
|||||||
/>
|
/>
|
||||||
<EntityTable
|
<EntityTable
|
||||||
v-else
|
v-else
|
||||||
|
@sort="(i) => emit('sort',i)"
|
||||||
:type="props.type"
|
:type="props.type"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:rows="filteredRows"
|
:rows="filteredRows"
|
||||||
|
:loading="props.loading"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const router = useRouter()
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const dataType = dataStore.dataTypes[type]
|
const dataType = dataStore.dataTypes[type]
|
||||||
|
|
||||||
@@ -97,6 +98,43 @@ const onTabChange = (index) => {
|
|||||||
router.push(`${router.currentRoute.value.path}?tabIndex=${index}`)
|
router.push(`${router.currentRoute.value.path}?tabIndex=${index}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changePinned = async () => {
|
||||||
|
let newPins = []
|
||||||
|
|
||||||
|
if(auth.profile.pinned_on_navigation.find(i => i.datatype === type && i.id === props.item.id)){
|
||||||
|
//Remove Pin
|
||||||
|
|
||||||
|
newPins = auth.profile.pinned_on_navigation.filter(i => !(i.datatype === type && i.id === props.item.id))
|
||||||
|
} else {
|
||||||
|
//Add Pin
|
||||||
|
|
||||||
|
newPins = [
|
||||||
|
...auth.profile.pinned_on_navigation,
|
||||||
|
{
|
||||||
|
id: props.item.id,
|
||||||
|
icon: "i-heroicons-document",
|
||||||
|
type: "standardEntity",
|
||||||
|
datatype: type,
|
||||||
|
label: props.item[dataType.templateColumns.find(i => i.title).key]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api(`/api/user/${auth.user.id}/profile`,{
|
||||||
|
method: "PUT",
|
||||||
|
body: {
|
||||||
|
data: {
|
||||||
|
pinned_on_navigation: newPins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await auth.fetchMe()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -147,6 +185,13 @@ const onTabChange = (index) => {
|
|||||||
>{{item ? `${dataType.labelSingle}${props.item[dataType.templateColumns.find(i => i.title).key] ? ': ' + props.item[dataType.templateColumns.find(i => i.title).key] : ''}`: '' }}</h1>
|
>{{item ? `${dataType.labelSingle}${props.item[dataType.templateColumns.find(i => i.title).key] ? ': ' + props.item[dataType.templateColumns.find(i => i.title).key] : ''}`: '' }}</h1>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
|
<UButton
|
||||||
|
v-if="auth.profile"
|
||||||
|
:variant="auth.profile?.pinned_on_navigation.find(i => i.datatype === type && i.id === props.item.id) ? 'solid' : 'outline'"
|
||||||
|
icon="i-heroicons-star"
|
||||||
|
color="yellow"
|
||||||
|
@click="changePinned"
|
||||||
|
></UButton>
|
||||||
<UButton
|
<UButton
|
||||||
@click="router.push(`/standardEntity/${type}/edit/${item.id}`)"
|
@click="router.push(`/standardEntity/${type}/edit/${item.id}`)"
|
||||||
>
|
>
|
||||||
@@ -158,22 +203,6 @@ const onTabChange = (index) => {
|
|||||||
v-else-if="!props.inModal && platform === 'mobile'"
|
v-else-if="!props.inModal && platform === 'mobile'"
|
||||||
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
|
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
|
||||||
>
|
>
|
||||||
<!-- <template #left>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-left"
|
|
||||||
variant="outline"
|
|
||||||
@click="router.back()/*router.push(`/standardEntity/${type}`)*/"
|
|
||||||
>
|
|
||||||
Zurück
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-left"
|
|
||||||
variant="outline"
|
|
||||||
@click="router.push(`/standardEntity/${type}`)"
|
|
||||||
>
|
|
||||||
Übersicht
|
|
||||||
</UButton>
|
|
||||||
</template>-->
|
|
||||||
<template #toggle>
|
<template #toggle>
|
||||||
<div></div>
|
<div></div>
|
||||||
</template>
|
</template>
|
||||||
@@ -191,7 +220,6 @@ const onTabChange = (index) => {
|
|||||||
</UButton>
|
</UButton>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
|
|
||||||
<UTabs
|
<UTabs
|
||||||
:items="dataType.showTabs"
|
:items="dataType.showTabs"
|
||||||
v-if="props.item.id && platform !== 'mobile'"
|
v-if="props.item.id && platform !== 'mobile'"
|
||||||
@@ -226,6 +254,7 @@ const onTabChange = (index) => {
|
|||||||
@updateNeeded="emit('updateNeeded')"
|
@updateNeeded="emit('updateNeeded')"
|
||||||
:platform="platform"
|
:platform="platform"
|
||||||
/>
|
/>
|
||||||
|
<!-- TODO Change Active Phase -->
|
||||||
<EntityShowSubPhases
|
<EntityShowSubPhases
|
||||||
:item="props.item"
|
:item="props.item"
|
||||||
:top-level-type="type"
|
:top-level-type="type"
|
||||||
@@ -259,13 +288,12 @@ const onTabChange = (index) => {
|
|||||||
v-else-if="tab.label === 'Zeiten'"
|
v-else-if="tab.label === 'Zeiten'"
|
||||||
:platform="platform"
|
:platform="platform"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<EntityShowSub
|
<EntityShowSub
|
||||||
:item="props.item"
|
:item="props.item"
|
||||||
:query-string-data="getAvailableQueryStringData()"
|
:query-string-data="getAvailableQueryStringData()"
|
||||||
:tab-label="tab.label"
|
:tab-label="tab.label"
|
||||||
:top-level-type="type"
|
:top-level-type="type"
|
||||||
|
:type="tab.key"
|
||||||
v-else
|
v-else
|
||||||
:platform="platform"
|
:platform="platform"
|
||||||
/>
|
/>
|
||||||
@@ -297,7 +325,7 @@ const onTabChange = (index) => {
|
|||||||
@updateNeeded="emit('updateNeeded')"
|
@updateNeeded="emit('updateNeeded')"
|
||||||
:platform="platform"
|
:platform="platform"
|
||||||
/>
|
/>
|
||||||
<EntityShowSubPhases
|
<!--<EntityShowSubPhases
|
||||||
:item="props.item"
|
:item="props.item"
|
||||||
:top-level-type="type"
|
:top-level-type="type"
|
||||||
v-else-if="sub.label === 'Phasen'"
|
v-else-if="sub.label === 'Phasen'"
|
||||||
@@ -326,7 +354,7 @@ const onTabChange = (index) => {
|
|||||||
:top-level-type="type"
|
:top-level-type="type"
|
||||||
v-else
|
v-else
|
||||||
:platform="platform"
|
:platform="platform"
|
||||||
/>
|
/>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</UDashboardPanelContent>
|
</UDashboardPanelContent>
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
let type = ref("")
|
let type = ref("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const tempStore = useTempStore()
|
const tempStore = useTempStore()
|
||||||
|
|
||||||
@@ -42,6 +40,7 @@ const columns = computed(() => dataType.templateColumns.filter((column) => !colu
|
|||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
|
|
||||||
const setup = () => {
|
const setup = () => {
|
||||||
|
|
||||||
if(!props.type && props.tabLabel ) {
|
if(!props.type && props.tabLabel ) {
|
||||||
if(props.tabLabel === "Aufgaben") {
|
if(props.tabLabel === "Aufgaben") {
|
||||||
type.value = "tasks"
|
type.value = "tasks"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useSum} from "~/composables/useSum.js";
|
import {useSum} from "~/composables/useSum.js";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
/*'/': () => {
|
/*'/': () => {
|
||||||
//console.log(searchinput)
|
//console.log(searchinput)
|
||||||
@@ -61,7 +60,8 @@ const router = useRouter()
|
|||||||
const createddocuments = ref([])
|
const createddocuments = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
createddocuments.value = (await useSupabaseSelect("createddocuments")).filter(i => !i.archived)
|
//createddocuments.value = (await useSupabaseSelect("createddocuments")).filter(i => !i.archived)
|
||||||
|
createddocuments.value = (await useEntities("createddocuments").select()).filter(i => !i.archived)
|
||||||
}
|
}
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
@@ -150,6 +150,7 @@ const selectItem = (item) => {
|
|||||||
<span>Ausgangsbelege</span>
|
<span>Ausgangsbelege</span>
|
||||||
</template>
|
</template>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
<!-- TODO Rendering when Screen is too small -->
|
||||||
<UButton
|
<UButton
|
||||||
@click="invoiceDeliveryNotes"
|
@click="invoiceDeliveryNotes"
|
||||||
v-if="props.topLevelType === 'projects'"
|
v-if="props.topLevelType === 'projects'"
|
||||||
@@ -179,7 +180,7 @@ const selectItem = (item) => {
|
|||||||
<UButton
|
<UButton
|
||||||
@click="showFinalInvoiceConfig = true"
|
@click="showFinalInvoiceConfig = true"
|
||||||
v-if="props.topLevelType === 'projects'"
|
v-if="props.topLevelType === 'projects'"
|
||||||
:disabled="!props.item.createddocuments.filter(i => !i.archived && i.type === 'advanceInvoices').length > 0"
|
:disabled="!props.item.createddocuments?.filter(i => !i.archived && i.type === 'advanceInvoices').length > 0"
|
||||||
>
|
>
|
||||||
+ Schlussrechnung
|
+ Schlussrechnung
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const availableFiles = ref([])
|
|||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
if(props.item.files) {
|
if(props.item.files) {
|
||||||
availableFiles.value = await files.selectSomeDocuments(props.item.files.map(i => i.id)) || []
|
availableFiles.value = (await files.selectSomeDocuments(props.item.files.map(i => i.id))) || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,12 +51,12 @@ setup()
|
|||||||
@uploadFinished="emit('updateNeeded')"
|
@uploadFinished="emit('updateNeeded')"
|
||||||
/>
|
/>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
<DocumentList
|
<DocumentList
|
||||||
:key="props.item.files.length"
|
:key="props.item.files.length"
|
||||||
:documents="availableFiles"
|
:documents="availableFiles"
|
||||||
v-if="availableFiles.length > 0"
|
v-if="availableFiles.length > 0"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UAlert
|
<UAlert
|
||||||
v-else
|
v-else
|
||||||
icon="i-heroicons-x-mark"
|
icon="i-heroicons-x-mark"
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ const dataType = dataStore.dataTypes[props.topLevelType]
|
|||||||
<template>
|
<template>
|
||||||
<UCard class="mt-5 scroll" :style="props.platform !== 'mobile' ? 'height: 80vh' : ''">
|
<UCard class="mt-5 scroll" :style="props.platform !== 'mobile' ? 'height: 80vh' : ''">
|
||||||
<HistoryDisplay
|
<HistoryDisplay
|
||||||
:type="dataType.historyItemHolder"
|
:type="props.topLevelType"
|
||||||
v-if="props
|
v-if="props.item.id"
|
||||||
.item.id"
|
|
||||||
:element-id="props.item.id"
|
:element-id="props.item.id"
|
||||||
render-headline
|
render-headline
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
rows: {
|
rows: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -40,9 +39,16 @@
|
|||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(["sort"]);
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -50,12 +56,20 @@
|
|||||||
const dataType = dataStore.dataTypes[props.type]
|
const dataType = dataStore.dataTypes[props.type]
|
||||||
|
|
||||||
const selectedItem = ref(0)
|
const selectedItem = ref(0)
|
||||||
|
const sort = ref({
|
||||||
|
column: dataType.supabaseSortColumn || "date",
|
||||||
|
direction: 'desc'
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UTable
|
<UTable
|
||||||
|
:loading="props.loading"
|
||||||
|
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
|
||||||
|
sort-mode="manual"
|
||||||
|
v-model:sort="sort"
|
||||||
|
@update:sort="emit('sort',{sort_column: sort.column, sort_direction: sort.direction})"
|
||||||
v-if="dataType && columns"
|
v-if="dataType && columns"
|
||||||
:rows="props.rows"
|
:rows="props.rows"
|
||||||
:columns="props.columns"
|
:columns="props.columns"
|
||||||
@@ -64,18 +78,26 @@
|
|||||||
@select="(i) => router.push(`/standardEntity/${type}/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` }"
|
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine ${dataType.label} anzuzeigen` }"
|
||||||
>
|
>
|
||||||
<template
|
<!-- <template
|
||||||
v-for="column in dataType.templateColumns.filter(i => !i.disabledInTable)"
|
v-for="column in dataType.templateColumns.filter(i => !i.disabledInTable)"
|
||||||
v-slot:[`${column.key}-header`]="{row}">
|
v-slot:[`${column.key}-header`]="{row}">
|
||||||
<span class="text-nowrap">{{column.label}}</span>
|
<span class="text-nowrap">{{column.label}}</span>
|
||||||
</template>
|
</template>-->
|
||||||
<template #name-data="{row}">
|
<template #name-data="{row}">
|
||||||
<span
|
<span
|
||||||
v-if="row.id === props.rows[selectedItem].id"
|
v-if="row.id === props.rows[selectedItem].id"
|
||||||
class="text-primary-500 font-bold">{{row.name}}
|
class="text-primary-500 font-bold">
|
||||||
</span>
|
<UTooltip
|
||||||
|
:text="row.name"
|
||||||
|
>
|
||||||
|
{{dataType.templateColumns.find(i => i.key === "name").maxLength ? (row.name.length > dataType.templateColumns.find(i => i.key === "name").maxLength ? `${row.name.substring(0,dataType.templateColumns.find(i => i.key === "name").maxLength)}...` : row.name ) : row.name}}
|
||||||
|
</UTooltip> </span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{row.name}}
|
<UTooltip
|
||||||
|
:text="row.name"
|
||||||
|
>
|
||||||
|
{{dataType.templateColumns.find(i => i.key === "name").maxLength ? (row.name.length > dataType.templateColumns.find(i => i.key === "name").maxLength ? `${row.name.substring(0,dataType.templateColumns.find(i => i.key === "name").maxLength)}...` : row.name ) : row.name}}
|
||||||
|
</UTooltip>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #fullName-data="{row}">
|
<template #fullName-data="{row}">
|
||||||
@@ -100,7 +122,12 @@
|
|||||||
v-for="column in dataType.templateColumns.filter(i => i.key !== 'name' && i.key !== 'fullName' && i.key !== 'licensePlate' && !i.disabledInTable)"
|
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}">
|
v-slot:[`${column.key}-data`]="{row}">
|
||||||
<component v-if="column.component" :is="column.component" :row="row"></component>
|
<component v-if="column.component" :is="column.component" :row="row"></component>
|
||||||
<span v-else>{{row[column.key] ? `${row[column.key]} ${column.unit ? column.unit : ''}`: ''}}</span>
|
<span v-else-if="row[column.key]">
|
||||||
|
<UTooltip :text="row[column.key]">
|
||||||
|
{{row[column.key] ? `${column.maxLength ? (row[column.key].length > column.maxLength ? `${row[column.key].substring(0,column.maxLength)}...` : row[column.key]) : row[column.key]} ${column.unit ? column.unit : ''}`: ''}}
|
||||||
|
</UTooltip>
|
||||||
|
</span>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</UTable>
|
</UTable>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ const links = [{
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
shortcuts.value = true
|
shortcuts.value = true
|
||||||
}
|
}
|
||||||
}, {
|
},/* {
|
||||||
label: 'Tickets',
|
label: 'Tickets',
|
||||||
icon: 'i-heroicons-clipboard-document',
|
icon: 'i-heroicons-clipboard-document',
|
||||||
to: '/support',
|
to: '/support',
|
||||||
}, {
|
},*/ {
|
||||||
label: 'Webseite',
|
label: 'Webseite',
|
||||||
icon: 'i-heroicons-globe-europe-africa',
|
icon: 'i-heroicons-globe-europe-africa',
|
||||||
to: 'https://fedeo.de',
|
to: 'https://fedeo.de',
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ const props = defineProps({
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const showAddHistoryItemModal = ref(false)
|
const showAddHistoryItemModal = ref(false)
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -28,11 +29,10 @@ const setup = async () => {
|
|||||||
if(await useCapacitor().getIsPhone()) platform.value = "mobile"
|
if(await useCapacitor().getIsPhone()) platform.value = "mobile"
|
||||||
|
|
||||||
if(props.type && props.elementId){
|
if(props.type && props.elementId){
|
||||||
items.value = (await supabase.from("historyitems").select().eq(props.type,props.elementId).order("created_at",{ascending: true})).data || []
|
items.value = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`)
|
||||||
} else {
|
} /*else {
|
||||||
items.value = (await supabase.from("historyitems").select().order("created_at",{ascending: true})).data || []
|
|
||||||
|
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
@@ -40,55 +40,24 @@ setup()
|
|||||||
|
|
||||||
|
|
||||||
const addHistoryItemData = ref({
|
const addHistoryItemData = ref({
|
||||||
text: "",
|
text: ""
|
||||||
config: {
|
|
||||||
type: props.type,
|
|
||||||
id: props.elementId
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const addHistoryItem = async () => {
|
const addHistoryItem = async () => {
|
||||||
console.log(addHistoryItemData.value)
|
|
||||||
addHistoryItemData.value.createdBy = profileStore.activeProfile.id
|
|
||||||
|
|
||||||
addHistoryItemData.value[props.type] = props.elementId
|
const res = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`, {
|
||||||
|
method: "POST",
|
||||||
const {data,error} = await supabase
|
body: addHistoryItemData.value
|
||||||
.from("historyitems")
|
|
||||||
.insert([{...addHistoryItemData.value, tenant: profileStore.currentTenant}])
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
if(addHistoryItemData.value.text.includes("@")){
|
|
||||||
let usernames = [...addHistoryItemData.value.text.matchAll(/@(\S*)/gm)]
|
|
||||||
|
|
||||||
const {data:profiles} = await supabase.from("profiles").select("id,username")
|
|
||||||
|
|
||||||
let notifications = usernames.map(i => {
|
|
||||||
let rawUsername = i[1]
|
|
||||||
|
|
||||||
return {
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
profile: profiles.find(x => x.username === rawUsername).id,
|
|
||||||
initiatingProfile: profileStore.activeProfile.id,
|
|
||||||
title: "Sie wurden im Logbuch erwähnt",
|
|
||||||
link: `/${props.type}s/show/${props.elementId}`,
|
|
||||||
message: addHistoryItemData.value.text
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(notifications)
|
|
||||||
|
|
||||||
const {error} = await supabase.from("notifications").insert(notifications)
|
|
||||||
}
|
|
||||||
|
|
||||||
addHistoryItemData.value = {}
|
addHistoryItemData.value = {}
|
||||||
toast.add({title: "Eintrag erfolgreich erstellt"})
|
toast.add({title: "Eintrag erfolgreich erstellt"})
|
||||||
showAddHistoryItemModal.value = false
|
showAddHistoryItemModal.value = false
|
||||||
await setup()
|
await setup()
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,15 +139,15 @@ const renderText = (text) => {
|
|||||||
/>
|
/>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<UAvatar
|
<UAvatar
|
||||||
v-if="!item.createdBy"
|
v-if="!item.created_by"
|
||||||
:src="colorMode.value === 'light' ? '/Logo.png' : '/Logo_Dark.png' "
|
:src="colorMode.value === 'light' ? '/Logo.png' : '/Logo_Dark.png' "
|
||||||
/>
|
/>
|
||||||
<UAvatar
|
<UAvatar
|
||||||
:alt="profileStore.getProfileById(item.createdBy).fullName"
|
:alt="item.created_by_profile?.full_name"
|
||||||
v-else
|
v-else
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<h3 v-if="item.createdBy">{{profileStore.getProfileById(item.createdBy) ? profileStore.getProfileById(item.createdBy).fullName : ""}}</h3>
|
<h3 v-if="item.created_by">{{item.created_by_profile?.full_name}}</h3>
|
||||||
<h3 v-else>FEDEO Bot</h3>
|
<h3 v-else>FEDEO Bot</h3>
|
||||||
<span v-html="renderText(item.text)"/><br>
|
<span v-html="renderText(item.text)"/><br>
|
||||||
<span class="text-gray-500">{{dayjs(item.created_at).format("DD.MM.YY HH:mm")}}</span>
|
<span class="text-gray-500">{{dayjs(item.created_at).format("DD.MM.YY HH:mm")}}</span>
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useRole} from "~/composables/useRole.js";
|
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
const {has} = usePermission()
|
||||||
|
|
||||||
const role = useRole()
|
|
||||||
const links = computed(() => {
|
const links = computed(() => {
|
||||||
return [
|
return [
|
||||||
... profileStore.currentTenant === 21 ? [{
|
...(auth.profile?.pinned_on_navigation || []).map(pin => {
|
||||||
label: "Lieferschein Formular",
|
if(pin.type === "external") {
|
||||||
to: "https://loar.ma-agrarservice.de/erfassung",
|
return {
|
||||||
icon: "i-heroicons-rectangle-stack",
|
label: pin.label,
|
||||||
|
to: pin.link,
|
||||||
|
icon: pin.icon,
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
}] : [],
|
pinned: true
|
||||||
... profileStore.currentTenant === 5 ? [{
|
}
|
||||||
|
}else if(pin.type === "standardEntity") {
|
||||||
|
return {
|
||||||
|
label: pin.label,
|
||||||
|
to: `/standardEntity/${pin.datatype}/show/${pin.id}`,
|
||||||
|
icon: pin.icon,
|
||||||
|
pinned: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
... false ? [{
|
||||||
label: "Support Tickets",
|
label: "Support Tickets",
|
||||||
to: "/support",
|
to: "/support",
|
||||||
icon: "i-heroicons-rectangle-stack",
|
icon: "i-heroicons-rectangle-stack",
|
||||||
@@ -34,22 +47,22 @@ const links = computed(() => {
|
|||||||
icon: "i-heroicons-rectangle-stack",
|
icon: "i-heroicons-rectangle-stack",
|
||||||
defaultOpen: false,
|
defaultOpen: false,
|
||||||
children: [
|
children: [
|
||||||
... role.checkRight("tasks") ? [{
|
... has("tasks") ? [{
|
||||||
label: "Aufgaben",
|
label: "Aufgaben",
|
||||||
to: "/standardEntity/tasks",
|
to: "/standardEntity/tasks",
|
||||||
icon: "i-heroicons-rectangle-stack"
|
icon: "i-heroicons-rectangle-stack"
|
||||||
}] : [],
|
}] : [],
|
||||||
... profileStore.ownTenant.features.planningBoard ? [{
|
... true ? [{
|
||||||
label: "Plantafel",
|
label: "Plantafel",
|
||||||
to: "/calendar/timeline",
|
to: "/calendar/timeline",
|
||||||
icon: "i-heroicons-calendar-days"
|
icon: "i-heroicons-calendar-days"
|
||||||
}] : [],
|
}] : [],
|
||||||
... profileStore.ownTenant.features.calendar ? [{
|
... true ? [{
|
||||||
label: "Kalender",
|
label: "Kalender",
|
||||||
to: "/calendar/grid",
|
to: "/calendar/grid",
|
||||||
icon: "i-heroicons-calendar-days"
|
icon: "i-heroicons-calendar-days"
|
||||||
}] : [],
|
}] : [],
|
||||||
... profileStore.ownTenant.features.calendar ? [{
|
... true ? [{
|
||||||
label: "Termine",
|
label: "Termine",
|
||||||
to: "/standardEntity/events",
|
to: "/standardEntity/events",
|
||||||
icon: "i-heroicons-calendar-days"
|
icon: "i-heroicons-calendar-days"
|
||||||
@@ -86,7 +99,7 @@ const links = computed(() => {
|
|||||||
label: "E-Mail",
|
label: "E-Mail",
|
||||||
to: "/email/new",
|
to: "/email/new",
|
||||||
icon: "i-heroicons-envelope"
|
icon: "i-heroicons-envelope"
|
||||||
}, {
|
}/*, {
|
||||||
label: "Logbücher",
|
label: "Logbücher",
|
||||||
to: "/communication/historyItems",
|
to: "/communication/historyItems",
|
||||||
icon: "i-heroicons-book-open"
|
icon: "i-heroicons-book-open"
|
||||||
@@ -94,25 +107,25 @@ const links = computed(() => {
|
|||||||
label: "Chats",
|
label: "Chats",
|
||||||
to: "/chats",
|
to: "/chats",
|
||||||
icon: "i-heroicons-chat-bubble-left"
|
icon: "i-heroicons-chat-bubble-left"
|
||||||
}
|
}*/
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
... (role.checkRight("customers") || role.checkRight("vendors") || role.checkRight("contacts")) ? [{
|
... (has("customers") || has("vendors") || has("contacts")) ? [{
|
||||||
label: "Kontakte",
|
label: "Kontakte",
|
||||||
defaultOpen: false,
|
defaultOpen: false,
|
||||||
icon: "i-heroicons-user-group",
|
icon: "i-heroicons-user-group",
|
||||||
children: [
|
children: [
|
||||||
... role.checkRight("customers") ? [{
|
... has("customers") ? [{
|
||||||
label: "Kunden",
|
label: "Kunden",
|
||||||
to: "/standardEntity/customers",
|
to: "/standardEntity/customers",
|
||||||
icon: "i-heroicons-user-group"
|
icon: "i-heroicons-user-group"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("vendors") ? [{
|
... has("vendors") ? [{
|
||||||
label: "Lieferanten",
|
label: "Lieferanten",
|
||||||
to: "/standardEntity/vendors",
|
to: "/standardEntity/vendors",
|
||||||
icon: "i-heroicons-truck"
|
icon: "i-heroicons-truck"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("contacts") ? [{
|
... has("contacts") ? [{
|
||||||
label: "Ansprechpartner",
|
label: "Ansprechpartner",
|
||||||
to: "/standardEntity/contacts",
|
to: "/standardEntity/contacts",
|
||||||
icon: "i-heroicons-user-group"
|
icon: "i-heroicons-user-group"
|
||||||
@@ -124,29 +137,29 @@ const links = computed(() => {
|
|||||||
defaultOpen:false,
|
defaultOpen:false,
|
||||||
icon: "i-heroicons-user-group",
|
icon: "i-heroicons-user-group",
|
||||||
children: [
|
children: [
|
||||||
... profileStore.ownTenant.features.timeTracking ? [{
|
... true ? [{
|
||||||
label: "Projektzeiten",
|
label: "Projektzeiten",
|
||||||
to: "/times",
|
to: "/times",
|
||||||
icon: "i-heroicons-clock"
|
icon: "i-heroicons-clock"
|
||||||
}] : [],
|
}] : [],
|
||||||
... profileStore.ownTenant.features.workingTimeTracking ? [{
|
... true ? [{
|
||||||
label: "Anwesenheiten",
|
label: "Anwesenheiten",
|
||||||
to: "/workingtimes",
|
to: "/workingtimes",
|
||||||
icon: "i-heroicons-clock"
|
icon: "i-heroicons-clock"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("absencerequests") ? [{
|
... has("absencerequests") ? [{
|
||||||
label: "Abwesenheiten",
|
label: "Abwesenheiten",
|
||||||
to: "/standardEntity/absencerequests",
|
to: "/standardEntity/absencerequests",
|
||||||
icon: "i-heroicons-document-text"
|
icon: "i-heroicons-document-text"
|
||||||
}] : [],
|
}] : [],
|
||||||
{
|
/*{
|
||||||
label: "Fahrten",
|
label: "Fahrten",
|
||||||
to: "/trackingTrips",
|
to: "/trackingTrips",
|
||||||
icon: "i-heroicons-map"
|
icon: "i-heroicons-map"
|
||||||
},
|
},*/
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
... profileStore.ownTenant.features.accounting ? [{
|
... [{
|
||||||
label: "Buchhaltung",
|
label: "Buchhaltung",
|
||||||
defaultOpen: false,
|
defaultOpen: false,
|
||||||
icon: "i-heroicons-chart-bar-square",
|
icon: "i-heroicons-chart-bar-square",
|
||||||
@@ -182,8 +195,8 @@ const links = computed(() => {
|
|||||||
icon: "i-heroicons-document-text"
|
icon: "i-heroicons-document-text"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},] : [],
|
}],
|
||||||
... role.checkRight("inventory") ? [{
|
... has("inventory") ? [{
|
||||||
label: "Lager",
|
label: "Lager",
|
||||||
icon: "i-heroicons-puzzle-piece",
|
icon: "i-heroicons-puzzle-piece",
|
||||||
defaultOpen: false,
|
defaultOpen: false,
|
||||||
@@ -197,7 +210,7 @@ const links = computed(() => {
|
|||||||
to: "/inventory/stocks",
|
to: "/inventory/stocks",
|
||||||
icon: "i-heroicons-square-3-stack-3d"
|
icon: "i-heroicons-square-3-stack-3d"
|
||||||
},*/
|
},*/
|
||||||
... role.checkRight("spaces") ? [{
|
... has("spaces") ? [{
|
||||||
label: "Lagerplätze",
|
label: "Lagerplätze",
|
||||||
to: "/standardEntity/spaces",
|
to: "/standardEntity/spaces",
|
||||||
icon: "i-heroicons-square-3-stack-3d"
|
icon: "i-heroicons-square-3-stack-3d"
|
||||||
@@ -209,22 +222,22 @@ const links = computed(() => {
|
|||||||
defaultOpen: false,
|
defaultOpen: false,
|
||||||
icon: "i-heroicons-clipboard-document",
|
icon: "i-heroicons-clipboard-document",
|
||||||
children: [
|
children: [
|
||||||
... role.checkRight("products") ? [{
|
... has("products") ? [{
|
||||||
label: "Artikel",
|
label: "Artikel",
|
||||||
to: "/standardEntity/products",
|
to: "/standardEntity/products",
|
||||||
icon: "i-heroicons-puzzle-piece"
|
icon: "i-heroicons-puzzle-piece"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("productcategories") ? [{
|
... has("productcategories") ? [{
|
||||||
label: "Artikelkategorien",
|
label: "Artikelkategorien",
|
||||||
to: "/standardEntity/productcategories",
|
to: "/standardEntity/productcategories",
|
||||||
icon: "i-heroicons-puzzle-piece"
|
icon: "i-heroicons-puzzle-piece"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("services") ? [{
|
... has("services") ? [{
|
||||||
label: "Leistungen",
|
label: "Leistungen",
|
||||||
to: "/standardEntity/services",
|
to: "/standardEntity/services",
|
||||||
icon: "i-heroicons-wrench-screwdriver"
|
icon: "i-heroicons-wrench-screwdriver"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("servicecategories") ? [{
|
... has("servicecategories") ? [{
|
||||||
label: "Leistungskategorien",
|
label: "Leistungskategorien",
|
||||||
to: "/standardEntity/servicecategories",
|
to: "/standardEntity/servicecategories",
|
||||||
icon: "i-heroicons-wrench-screwdriver"
|
icon: "i-heroicons-wrench-screwdriver"
|
||||||
@@ -239,70 +252,67 @@ const links = computed(() => {
|
|||||||
to: "/standardEntity/hourrates",
|
to: "/standardEntity/hourrates",
|
||||||
icon: "i-heroicons-user-group"
|
icon: "i-heroicons-user-group"
|
||||||
},
|
},
|
||||||
... role.checkRight("vehicles") ? [{
|
... has("vehicles") ? [{
|
||||||
label: "Fahrzeuge",
|
label: "Fahrzeuge",
|
||||||
to: "/standardEntity/vehicles",
|
to: "/standardEntity/vehicles",
|
||||||
icon: "i-heroicons-truck"
|
icon: "i-heroicons-truck"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("inventoryitems") ? [{
|
... has("inventoryitems") ? [{
|
||||||
label: "Inventar",
|
label: "Inventar",
|
||||||
to: "/standardEntity/inventoryitems",
|
to: "/standardEntity/inventoryitems",
|
||||||
icon: "i-heroicons-puzzle-piece"
|
icon: "i-heroicons-puzzle-piece"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("inventoryitems") ? [{
|
... has("inventoryitems") ? [{
|
||||||
label: "Inventargruppen",
|
label: "Inventargruppen",
|
||||||
to: "/standardEntity/inventoryitemgroups",
|
to: "/standardEntity/inventoryitemgroups",
|
||||||
icon: "i-heroicons-puzzle-piece"
|
icon: "i-heroicons-puzzle-piece"
|
||||||
}] : [],
|
}] : [],
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
... role.checkRight("checks") ? [{
|
|
||||||
label: "Überprüfungen",
|
... has("projects") ? [{
|
||||||
to: "/standardEntity/checks",
|
|
||||||
icon: "i-heroicons-magnifying-glass"
|
|
||||||
},] : [],
|
|
||||||
... role.checkRight("projects") ? [{
|
|
||||||
label: "Projekte",
|
label: "Projekte",
|
||||||
to: "/standardEntity/projects",
|
to: "/standardEntity/projects",
|
||||||
icon: "i-heroicons-clipboard-document-check"
|
icon: "i-heroicons-clipboard-document-check"
|
||||||
},] : [],
|
},] : [],
|
||||||
... role.checkRight("contracts") ? [{
|
... has("contracts") ? [{
|
||||||
label: "Verträge",
|
label: "Verträge",
|
||||||
to: "/standardEntity/contracts",
|
to: "/standardEntity/contracts",
|
||||||
icon: "i-heroicons-clipboard-document"
|
icon: "i-heroicons-clipboard-document"
|
||||||
}] : [],
|
}] : [],
|
||||||
... role.checkRight("plants") ? [{
|
... has("plants") ? [{
|
||||||
label: "Objekte",
|
label: "Objekte",
|
||||||
to: "/standardEntity/plants",
|
to: "/standardEntity/plants",
|
||||||
icon: "i-heroicons-clipboard-document"
|
icon: "i-heroicons-clipboard-document"
|
||||||
},] : [],
|
},] : [],
|
||||||
|
... has("checks") ? [{
|
||||||
|
label: "Überprüfungen",
|
||||||
|
to: "/standardEntity/checks",
|
||||||
|
icon: "i-heroicons-magnifying-glass"
|
||||||
|
},] : [],
|
||||||
{
|
{
|
||||||
label: "Einstellungen",
|
label: "Einstellungen",
|
||||||
defaultOpen: false,
|
defaultOpen: false,
|
||||||
icon: "i-heroicons-cog-8-tooth",
|
icon: "i-heroicons-cog-8-tooth",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: "Abrechnung",
|
|
||||||
to: "https://billing.stripe.com/p/login/cN29Eb32Vdx0gOk288",
|
|
||||||
icon: "i-heroicons-document-currency-euro",
|
|
||||||
target: "_blank"
|
|
||||||
},{
|
|
||||||
label: "Nummernkreise",
|
label: "Nummernkreise",
|
||||||
to: "/settings/numberRanges",
|
to: "/settings/numberRanges",
|
||||||
icon: "i-heroicons-clipboard-document-list"
|
icon: "i-heroicons-clipboard-document-list"
|
||||||
},{
|
},/*{
|
||||||
label: "Rollen",
|
label: "Rollen",
|
||||||
to: "/roles",
|
to: "/roles",
|
||||||
icon: "i-heroicons-key"
|
icon: "i-heroicons-key"
|
||||||
},{
|
},*/{
|
||||||
label: "E-Mail Konten",
|
label: "E-Mail Konten",
|
||||||
to: "/settings/emailAccounts",
|
to: "/settings/emailAccounts",
|
||||||
icon: "i-heroicons-envelope"
|
icon: "i-heroicons-envelope",
|
||||||
|
disabled: true
|
||||||
},{
|
},{
|
||||||
label: "Bankkonten",
|
label: "Bankkonten",
|
||||||
to: "/settings/banking",
|
to: "/settings/banking",
|
||||||
icon: "i-heroicons-currency-euro"
|
icon: "i-heroicons-currency-euro",
|
||||||
|
disabled: true
|
||||||
},{
|
},{
|
||||||
label: "Textvorlagen",
|
label: "Textvorlagen",
|
||||||
to: "/settings/texttemplates",
|
to: "/settings/texttemplates",
|
||||||
@@ -328,25 +338,54 @@ const links = computed(() => {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// nur Items mit Children → für Accordion
|
||||||
|
const accordionItems = computed(() =>
|
||||||
|
links.value.filter(item => Array.isArray(item.children) && item.children.length > 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// nur Items ohne Children → als Buttons
|
||||||
|
const buttonItems = computed(() =>
|
||||||
|
links.value.filter(item => !item.children || item.children.length === 0)
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- Standalone Buttons -->
|
||||||
<div
|
<div class="flex flex-col gap-1">
|
||||||
v-for="item in links"
|
|
||||||
>
|
|
||||||
<UAccordion
|
|
||||||
v-if="item.children"
|
|
||||||
:items="[item]"
|
|
||||||
>
|
|
||||||
<template #default="{item,index,open}">
|
|
||||||
<UButton
|
<UButton
|
||||||
variant="ghost"
|
v-for="item in buttonItems"
|
||||||
:color="item.children.find(i => route.path.includes(i.to)) ? 'primary' : 'gray'"
|
:key="item.label"
|
||||||
|
:variant="item.pinned ? 'ghost' : 'ghost'"
|
||||||
|
:color="(item.to && route.path === item.to) ? 'primary' : (item.pinned ? 'amber' : 'gray')"
|
||||||
|
:icon="item.pinned ? 'i-heroicons-star' : item.icon"
|
||||||
|
class="w-full"
|
||||||
|
:to="item.to"
|
||||||
|
:target="item.target"
|
||||||
|
>
|
||||||
|
<UIcon
|
||||||
|
v-if="item.pinned"
|
||||||
|
:name="item.icon"
|
||||||
|
class="w-5 h-5 me-2"
|
||||||
|
/>
|
||||||
|
{{ item.label }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
<UDivider/>
|
||||||
|
<!-- Accordion für die Items mit Children -->
|
||||||
|
<UAccordion
|
||||||
|
:items="accordionItems"
|
||||||
|
:multiple="false"
|
||||||
|
class="mt-2"
|
||||||
|
>
|
||||||
|
<template #default="{ item, open }">
|
||||||
|
<UButton
|
||||||
|
:variant="'ghost'"
|
||||||
|
:color="(item.children?.some(c => route.path.includes(c.to))) ? 'primary' : 'gray'"
|
||||||
:icon="item.icon"
|
:icon="item.icon"
|
||||||
|
class="w-full"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
|
|
||||||
<template #trailing>
|
<template #trailing>
|
||||||
<UIcon
|
<UIcon
|
||||||
name="i-heroicons-chevron-right-20-solid"
|
name="i-heroicons-chevron-right-20-solid"
|
||||||
@@ -356,13 +395,61 @@ const links = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
</UButton>
|
</UButton>
|
||||||
</template>
|
</template>
|
||||||
<template #item="{item, open}">
|
|
||||||
|
<template #item="{ item }">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<UButton
|
<UButton
|
||||||
|
v-for="child in item.children"
|
||||||
|
:key="child.label"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
:color="child.to === route.path ? 'primary' : 'gray'"
|
:color="child.to === route.path ? 'primary' : 'gray'"
|
||||||
:icon="child.icon"
|
:icon="child.icon"
|
||||||
|
class="ml-4"
|
||||||
|
:to="child.to"
|
||||||
|
:target="child.target"
|
||||||
|
:disabled="child.disabled"
|
||||||
|
>
|
||||||
|
{{ child.label }}
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UAccordion>
|
||||||
|
<!-- <UAccordion
|
||||||
|
:items="links"
|
||||||
|
:multiple="false"
|
||||||
|
>
|
||||||
|
<template #default="{ item, index, open }">
|
||||||
|
<UButton
|
||||||
|
:variant="item.pinned ? 'ghost' : 'ghost'"
|
||||||
|
:color="(item.to && route.path === item.to) || (item.children?.some(c => route.path.includes(c.to))) ? 'primary' : (item.pinned ? 'amber' : 'gray')"
|
||||||
|
:icon="item.pinned ? 'i-heroicons-star' : item.icon"
|
||||||
|
class="w-full"
|
||||||
|
:to="item.to"
|
||||||
|
:target="item.target"
|
||||||
|
>
|
||||||
|
<UIcon
|
||||||
|
v-if="item.pinned"
|
||||||
|
:name="item.icon" class="w-5 h-5 me-2" />
|
||||||
|
{{ item.label }}
|
||||||
|
|
||||||
|
<template v-if="item.children" #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 class="flex flex-col" v-if="item.children?.length > 0">
|
||||||
|
<UButton
|
||||||
v-for="child in item.children"
|
v-for="child in item.children"
|
||||||
|
:key="child.label"
|
||||||
|
variant="ghost"
|
||||||
|
:color="child.to === route.path ? 'primary' : 'gray'"
|
||||||
|
:icon="child.icon"
|
||||||
class="ml-4"
|
class="ml-4"
|
||||||
:to="child.to"
|
:to="child.to"
|
||||||
:target="child.target"
|
:target="child.target"
|
||||||
@@ -371,16 +458,6 @@ const links = computed(() => {
|
|||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UAccordion>
|
</UAccordion>-->
|
||||||
<UButton
|
|
||||||
v-else
|
|
||||||
variant="ghost"
|
|
||||||
:color="item.to === route.path ? 'primary' : 'gray'"
|
|
||||||
class="w-full"
|
|
||||||
:icon="item.icon"
|
|
||||||
:to="item.to"
|
|
||||||
>
|
|
||||||
{{item.label}}
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
212
components/PDFViewer.client.vue
Normal file
212
components/PDFViewer.client.vue
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from "vue"
|
||||||
|
import { VPdfViewer, useLicense } from "@vue-pdf-viewer/viewer"
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// Beispiel: "FEDEO/26/filesbyid/11990345-8711-4e23-8851-c50f028fc915/RE25-1081.pdf"
|
||||||
|
fileId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
uri: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
type: Number,
|
||||||
|
default: 1.2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
useLicense(config.public.pdfLicense)
|
||||||
|
|
||||||
|
const pdfSrc = ref(null) // ObjectURL fürs Viewer
|
||||||
|
const { $api } = useNuxtApp()
|
||||||
|
|
||||||
|
async function loadPdf(id) {
|
||||||
|
try {
|
||||||
|
const arrayBuffer = await $api(`/api/files/download/${id}`, {
|
||||||
|
method: "POST",
|
||||||
|
responseType: "arrayBuffer", // wichtig für pdf.js
|
||||||
|
})
|
||||||
|
const blob = new Blob([arrayBuffer], { type: "application/pdf" })
|
||||||
|
pdfSrc.value = URL.createObjectURL(blob)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fehler beim Laden der PDF:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if(props.fileId) {
|
||||||
|
loadPdf(props.fileId)
|
||||||
|
} else if(props.uri) {
|
||||||
|
pdfSrc.value = props.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
//window.addEventListener("resize", handleZoomTool("pageWidth"), true);
|
||||||
|
|
||||||
|
})
|
||||||
|
watch(() => props.fileId, (newPath) => {
|
||||||
|
if (newPath) loadPdf(newPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
const vpvRef = ref(null);
|
||||||
|
|
||||||
|
//Zoom Control
|
||||||
|
|
||||||
|
const zoomControl = computed(() => vpvRef.value?.zoomControl)
|
||||||
|
|
||||||
|
const currentScale = computed(() => {
|
||||||
|
return zoomControl.value?.scale
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleZoomTool = (type) => {
|
||||||
|
console.log(type)
|
||||||
|
const zoomCtrl = unref(zoomControl)
|
||||||
|
if (!zoomCtrl) return
|
||||||
|
|
||||||
|
const scale = unref(currentScale)
|
||||||
|
if (type === "in") {
|
||||||
|
scale && zoomCtrl.zoom(scale + 0.25)
|
||||||
|
} else if (type === "out") {
|
||||||
|
scale && zoomCtrl.zoom(scale - 0.25)
|
||||||
|
} else {
|
||||||
|
zoomCtrl.zoom(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Page Control
|
||||||
|
const pageControl = computed(() => vpvRef.value?.pageControl)
|
||||||
|
const currentPageInput = computed(() => pageControl.value?.currentPage)
|
||||||
|
const searchControl = computed(() => vpvRef.value?.searchControl)
|
||||||
|
const totalMatches = computed(() => searchControl.value?.searchMatches?.totalMatches)
|
||||||
|
|
||||||
|
const isNextPageButtonDisable = computed(() =>
|
||||||
|
pageControl.value?.currentPage === pageControl.value?.totalPages
|
||||||
|
)
|
||||||
|
|
||||||
|
const isPreviousPageButtonDisable = computed(() =>
|
||||||
|
pageControl.value?.currentPage === 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const prevPage = () => {
|
||||||
|
const isFirstPage = pageControl.value?.currentPage === 1
|
||||||
|
if (isFirstPage) return
|
||||||
|
pageControl.value?.goToPage(pageControl.value?.currentPage - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPage = () => {
|
||||||
|
const isLastPage = pageControl.value?.currentPage === pageControl.value?.totalPages
|
||||||
|
if (isLastPage) return
|
||||||
|
pageControl.value?.goToPage(pageControl.value?.currentPage + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyPress = (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
handlePageInput(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle Download
|
||||||
|
const downloadControl = computed(() => vpvRef.value?.downloadControl)
|
||||||
|
|
||||||
|
const handleDownloadFile = async () => {
|
||||||
|
await useFiles().downloadFile(props.fileId)
|
||||||
|
|
||||||
|
/*const downloadCtrl = unref(downloadControl)
|
||||||
|
if (!downloadCtrl) return
|
||||||
|
downloadCtrl.download()*/
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(downloadControl, (downloadCtrl) => {
|
||||||
|
if (!downloadCtrl) return
|
||||||
|
|
||||||
|
downloadCtrl.onError = (error) => {
|
||||||
|
console.log("Download error", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadCtrl.onComplete = () => {
|
||||||
|
console.log("Download completed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-4 justify-self-center">
|
||||||
|
<div class="flex items-center gap-4 text-[#7862FF] bg-pale-blue border-[#D7D1FB] rounded-lg p-2 justify-center">
|
||||||
|
|
||||||
|
<!-- Zoom out button -->
|
||||||
|
<UButton
|
||||||
|
@click="() => handleZoomTool('pageWidth')"
|
||||||
|
icon="i-heroicons-document-text"
|
||||||
|
variant="outline"
|
||||||
|
></UButton>
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
@click="() => handleZoomTool('out')"
|
||||||
|
icon="i-heroicons-magnifying-glass-minus"
|
||||||
|
variant="outline"
|
||||||
|
></UButton>
|
||||||
|
|
||||||
|
<!-- Zoom in button -->
|
||||||
|
<UButton
|
||||||
|
@click="() => handleZoomTool('in')"
|
||||||
|
icon="i-heroicons-magnifying-glass-plus"
|
||||||
|
variant="outline"
|
||||||
|
></UButton>
|
||||||
|
<UButton
|
||||||
|
@click="handleDownloadFile"
|
||||||
|
variant="outline"
|
||||||
|
icon="i-heroicons-arrow-down-on-square"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="prevPage"
|
||||||
|
:disabled="isPreviousPageButtonDisable"
|
||||||
|
icon="i-heroicons-chevron-up"
|
||||||
|
variant="outline"
|
||||||
|
></UButton>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Page number input and total pages display -->
|
||||||
|
<div class="flex items-center text-sm font-normal">
|
||||||
|
<UInput
|
||||||
|
v-model="currentPageInput"
|
||||||
|
class="w-24 h-8 rounded-sm focus:outline-none"
|
||||||
|
@change="handleKeyPress"
|
||||||
|
>
|
||||||
|
<template #trailing>
|
||||||
|
/ {{ pageControl?.totalPages }}
|
||||||
|
</template>
|
||||||
|
</UInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Next page button -->
|
||||||
|
<UButton
|
||||||
|
@click="nextPage"
|
||||||
|
:disabled="isNextPageButtonDisable"
|
||||||
|
icon="i-heroicons-chevron-down"
|
||||||
|
variant="outline"
|
||||||
|
></UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pdf-container">
|
||||||
|
<VPdfViewer
|
||||||
|
v-if="pdfSrc"
|
||||||
|
:src="pdfSrc"
|
||||||
|
style="height: 78vh; width: 100%;"
|
||||||
|
:toolbar-options="false"
|
||||||
|
ref="vpvRef"
|
||||||
|
/>
|
||||||
|
<div v-else>
|
||||||
|
<UProgress
|
||||||
|
class="mt-5 w-2/3 mx-auto"
|
||||||
|
animation="carousel"></UProgress>
|
||||||
|
</div>
|
||||||
|
</div></template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pdf-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const selectedProfile = ref(profileStore.activeProfile.id)
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<USelectMenu
|
|
||||||
:options="profileStore.ownProfiles"
|
|
||||||
value-attribute="id"
|
|
||||||
class="w-40"
|
|
||||||
@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="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">{{profileStore.tenants.find(i => profileStore.profiles.find(i => i.id === selectedProfile).tenant === i.id).name}}</span>
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
<template #option="{option}">
|
|
||||||
{{profileStore.tenants.find(i => i.id === option.tenant).name}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</template>
|
|
||||||
@@ -35,10 +35,10 @@ const item = ref({})
|
|||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
if(props.mode === "show") {
|
if(props.mode === "show") {
|
||||||
//Load Data for Show
|
//Load Data for Show
|
||||||
item.value = await useSupabaseSelectSingle(props.type, props.id, dataType.supabaseSelectWithInformation || "*")
|
item.value = await useEntities(props.type).selectSingle(props.id, dataType.supabaseSelectWithInformation || "*")
|
||||||
} else if(props.mode === "edit") {
|
} else if(props.mode === "edit") {
|
||||||
//Load Data for Edit
|
//Load Data for Edit
|
||||||
const data = JSON.stringify((await supabase.from(props.type).select().eq("id", props.id).single()).data)
|
const data = JSON.stringify(await useEntities(props.type).selectSingle(props.id)/*(await supabase.from(props.type).select().eq("id", props.id).single()).data*/)
|
||||||
//await useSupabaseSelectSingle(type, route.params.id)
|
//await useSupabaseSelectSingle(type, route.params.id)
|
||||||
item.value = data
|
item.value = data
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
} else if(props.mode === "list") {
|
} else if(props.mode === "list") {
|
||||||
//Load Data for List
|
//Load Data for List
|
||||||
items.value = await useSupabaseSelect(props.type, dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn,dataType.supabaseSortAscending || false)
|
items.value = await useEntities(props.type).select(dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn,dataType.supabaseSortAscending || false)
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
|
|||||||
27
components/TenantDropdown.vue
Normal file
27
components/TenantDropdown.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup>
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
const selectedTenant = ref(auth.user.tenant_id)
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<USelectMenu
|
||||||
|
:options="auth.tenants"
|
||||||
|
value-attribute="id"
|
||||||
|
class="w-40"
|
||||||
|
@change="auth.switchTenant(selectedTenant)"
|
||||||
|
v-model="selectedTenant"
|
||||||
|
>
|
||||||
|
<UButton color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" class="w-full">
|
||||||
|
<UAvatar :alt="auth.activeTenantData?.name" size="md" />
|
||||||
|
|
||||||
|
<span class="truncate text-gray-900 dark:text-white font-semibold">{{auth.tenants.find(i => auth.activeTenant === i.id).name}}</span>
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<template #option="{option}">
|
||||||
|
{{option.name}}
|
||||||
|
</template>
|
||||||
|
</USelectMenu>
|
||||||
|
</template>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
const { isHelpSlideoverOpen } = useDashboard()
|
const { isHelpSlideoverOpen } = useDashboard()
|
||||||
const { isDashboardSearchModalOpen } = useUIState()
|
const { isDashboardSearchModalOpen } = useUIState()
|
||||||
const { metaSymbol } = useShortcuts()
|
const { metaSymbol } = useShortcuts()
|
||||||
@@ -7,23 +8,26 @@ const dataStore = useDataStore()
|
|||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const supabase = useSupabaseClient()
|
const supabase = useSupabaseClient()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const items = computed(() => [
|
const items = computed(() => [
|
||||||
[{
|
[{
|
||||||
slot: 'account',
|
slot: 'account',
|
||||||
label: '',
|
label: '',
|
||||||
disabled: true
|
disabled: true
|
||||||
}], [{
|
}], [/*{
|
||||||
label: 'Mein Profil',
|
label: 'Mein Profil',
|
||||||
icon: 'i-heroicons-user',
|
icon: 'i-heroicons-user',
|
||||||
to: `/profiles/show/${profileStore.activeProfile.id}`
|
to: `/profiles/show/${profileStore.activeProfile.id}`
|
||||||
|
},*/{
|
||||||
|
label: 'Passwort ändern',
|
||||||
|
icon: 'i-heroicons-shield-check',
|
||||||
|
to: `/password-change`
|
||||||
},{
|
},{
|
||||||
label: 'Abmelden',
|
label: 'Abmelden',
|
||||||
icon: 'i-heroicons-arrow-left-on-rectangle',
|
icon: 'i-heroicons-arrow-left-on-rectangle',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
await supabase.auth.signOut()
|
await auth.logout()
|
||||||
//await dataStore.clearStore()
|
|
||||||
await router.push('/login')
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@@ -33,10 +37,10 @@ const items = computed(() => [
|
|||||||
<template>
|
<template>
|
||||||
<UDropdown mode="hover" :items="items" :ui="{ width: 'w-full', item: { disabled: 'cursor-text select-text' } }" :popper="{ strategy: 'absolute', placement: 'top' }" class="w-full">
|
<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 }">
|
<template #default="{ open }">
|
||||||
<UButton color="gray" variant="ghost" class="w-full" :label="profileStore.activeProfile.fullName" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
<UButton color="gray" variant="ghost" class="w-full" :label="auth.user.email" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||||
<template #leading>
|
<!-- <template #leading>
|
||||||
<UAvatar :alt="profileStore.activeProfile ? profileStore.activeProfile.fullName : ''" size="xs" />
|
<UAvatar :alt="auth.user.email" size="xs" />
|
||||||
</template>
|
</template>-->
|
||||||
|
|
||||||
<template #trailing>
|
<template #trailing>
|
||||||
<UIcon name="i-heroicons-ellipsis-vertical" class="w-5 h-5 ml-auto" />
|
<UIcon name="i-heroicons-ellipsis-vertical" class="w-5 h-5 ml-auto" />
|
||||||
@@ -50,7 +54,7 @@ const items = computed(() => [
|
|||||||
Angemeldet als
|
Angemeldet als
|
||||||
</p>
|
</p>
|
||||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||||
{{profileStore.activeProfile.email}}
|
{{auth.user.email}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span v-if="props.row.driver">{{props.row.driver ? profileStore.getProfileById(props.row.driver).fullName : props.row.driver}}</span>
|
<span v-if="props.row.driver">{{props.row.driver ? "" : props.row.driver}}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const props = defineProps({
|
|||||||
const incomingInvoices = ref({})
|
const incomingInvoices = ref({})
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
incomingInvoices.value = (await supabase.from("incominginvoices").select().eq("tenant", profileStore.currentTenant)).data.filter(i => i.accounts.find(x => x.costCentre === props.item.id))
|
incomingInvoices.value = (await useEntities("incominginvoices").select()).filter(i => i.accounts.find(x => x.costCentre === props.item.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
let unallocatedStatements = ref(0)
|
let unallocatedStatements = ref(0)
|
||||||
let bankaccounts = ref([])
|
let bankaccounts = ref([])
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
let bankstatements = (await useSupabaseSelect("bankstatements","*, statementallocations(*)","date",true)).filter(i => !i.archived)
|
let bankstatements = (await useEntities("bankstatements").select("*, statementallocations(*)","date",true)).filter(i => !i.archived)
|
||||||
unallocatedStatements.value = bankstatements.filter(i => Number(calculateOpenSum(i)) !== 0).length
|
unallocatedStatements.value = bankstatements.filter(i => Number(calculateOpenSum(i)) !== 0).length
|
||||||
bankaccounts.value = await useSupabaseSelect("bankaccounts")
|
bankaccounts.value = await useEntities("bankaccounts").select()
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
|
|||||||
@@ -4,21 +4,20 @@ import dayjs from "dayjs";
|
|||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
let incomeData = ref({})
|
let incomeData = ref({})
|
||||||
let expenseData = ref({})
|
let expenseData = ref({})
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",profileStore.currentTenant).eq("state","Gebucht").in('type',['invoices','advanceInvoices','cancellationInvoices'])).data
|
//let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",profileStore.currentTenant).eq("state","Gebucht").in('type',['invoices','advanceInvoices','cancellationInvoices'])).data
|
||||||
console.log(incomeRawData)
|
let incomeRawData = (await useEntities("createddocuments").select()).filter(i => i.state === "Gebucht" && ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type))
|
||||||
|
|
||||||
let incomeRawFilteredData = incomeRawData.filter(x => x.state === 'Gebucht' && incomeRawData.find(i => i.linkedDocument && i.linkedDocument.id === x.id && i.type === 'cancellationInvoices') && ['invoices','advanceInvoices'].includes(row.type))
|
let incomeRawFilteredData = incomeRawData.filter(x => x.state === 'Gebucht' && incomeRawData.find(i => i.linkedDocument && i.linkedDocument.id === x.id && i.type === 'cancellationInvoices') && ['invoices','advanceInvoices'].includes(row.type))
|
||||||
|
|
||||||
|
|
||||||
let expenseRawData =(await supabase.from("incominginvoices").select().eq("tenant",profileStore.currentTenant)).data
|
//let expenseRawData =(await supabase.from("incominginvoices").select().eq("tenant",profileStore.currentTenant)).data
|
||||||
let withoutInvoiceRawData = (await supabase.from("statementallocations").select().eq("tenant",profileStore.currentTenant).not("account","is",null)).data
|
let expenseRawData =(await useEntities("incominginvoices").select())
|
||||||
|
//let withoutInvoiceRawData = (await supabase.from("statementallocations").select().eq("tenant",profileStore.currentTenant).not("account","is",null)).data
|
||||||
|
let withoutInvoiceRawData = (await useEntities("statementallocations").select()).filter(i => i.account)
|
||||||
|
|
||||||
let withoutInvoiceRawDataExpenses = []
|
let withoutInvoiceRawDataExpenses = []
|
||||||
let withoutInvoiceRawDataIncomes = []
|
let withoutInvoiceRawDataIncomes = []
|
||||||
@@ -87,7 +86,7 @@ const setup = async () => {
|
|||||||
}
|
}
|
||||||
Object.keys(expenseMonths).forEach(month => {
|
Object.keys(expenseMonths).forEach(month => {
|
||||||
let dates = Object.keys(expenseData.value).filter(i => i.split("-")[1] === month && i.split("-")[2] === dayjs().format("YY"))
|
let dates = Object.keys(expenseData.value).filter(i => i.split("-")[1] === month && i.split("-")[2] === dayjs().format("YY"))
|
||||||
console.log(dates)
|
|
||||||
|
|
||||||
|
|
||||||
dates.forEach(date => {
|
dates.forEach(date => {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const profileStore = useProfileStore();
|
const profileStore = useProfileStore();
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
let unpaidInvoicesSum = ref(0)
|
let unpaidInvoicesSum = ref(0)
|
||||||
let unpaidInvoicesCount = ref(0)
|
let unpaidInvoicesCount = ref(0)
|
||||||
@@ -15,7 +14,7 @@ let draftInvoicesCount = ref(0)
|
|||||||
let countPreparedOpenIncomingInvoices = ref(0)
|
let countPreparedOpenIncomingInvoices = ref(0)
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
let items = (await useSupabaseSelect("createddocuments","*, statementallocations(*), customer(id,name), linkedDocument(*)")).filter(i => !i.archived)
|
let items = (await useEntities("createddocuments").select("*, statementallocations(*), customer(id,name), linkedDocument(*)")).filter(i => !i.archived)
|
||||||
let documents = items.filter(i => i.type === "invoices" ||i.type === "advanceInvoices")
|
let documents = items.filter(i => i.type === "invoices" ||i.type === "advanceInvoices")
|
||||||
|
|
||||||
let draftDocuments = documents.filter(i => i.state === "Entwurf")
|
let draftDocuments = documents.filter(i => i.state === "Entwurf")
|
||||||
@@ -25,11 +24,9 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
finalizedDocuments = finalizedDocuments.filter(x => (x.type === 'invoices' || x.type === 'advanceInvoices') && x.state === 'Gebucht' && !items.find(i => i.linkedDocument && i.linkedDocument.id === x.id))
|
finalizedDocuments = finalizedDocuments.filter(x => (x.type === 'invoices' || x.type === 'advanceInvoices') && x.state === 'Gebucht' && !items.find(i => i.linkedDocument && i.linkedDocument.id === x.id))
|
||||||
|
|
||||||
console.log(finalizedDocuments)
|
|
||||||
|
|
||||||
finalizedDocuments.forEach(i => {
|
finalizedDocuments.forEach(i => {
|
||||||
//if(process.dev) console.log(i)
|
|
||||||
//if(process.dev) console.log(useSum().getCreatedDocumentSum(i, documents) - i.statementallocations.reduce((n,{amount}) => n + amount, 0))
|
|
||||||
if(dayjs().subtract(i.paymentDays,"days").isAfter(i.documentDate)) {
|
if(dayjs().subtract(i.paymentDays,"days").isAfter(i.documentDate)) {
|
||||||
unpaidOverdueInvoicesSum.value += useSum().getCreatedDocumentSum(i, documents) - i.statementallocations.reduce((n,{amount}) => n + amount, 0)
|
unpaidOverdueInvoicesSum.value += useSum().getCreatedDocumentSum(i, documents) - i.statementallocations.reduce((n,{amount}) => n + amount, 0)
|
||||||
unpaidOverdueInvoicesCount.value += 1
|
unpaidOverdueInvoicesCount.value += 1
|
||||||
@@ -45,7 +42,7 @@ const setupPage = async () => {
|
|||||||
})
|
})
|
||||||
draftInvoicesCount.value = draftDocuments.length
|
draftInvoicesCount.value = draftDocuments.length
|
||||||
|
|
||||||
countPreparedOpenIncomingInvoices.value = (await supabase.from("incominginvoices").select("id").eq("tenant",profileStore.currentTenant).eq("state", "Vorbereitet")).data.length
|
countPreparedOpenIncomingInvoices.value = (await useEntities("incominginvoices").select("id, state")).filter(i => i.state === "Vorbereitet").length
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
const openTasks = ref([])
|
const openTasks = ref([])
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
openTasks.value = (await supabase.from("tasks").select().eq("tenant",useProfileStore().currentTenant).not("archived","is",true).neq("categorie","Abgeschlossen").eq("profile", useProfileStore().activeProfile.id)).data
|
//TODO: BACKEND CHANGE Migrate to auth_users for profile
|
||||||
|
openTasks.value = (await useEntities("tasks").select()).filter(i => !i.archived && i.user_id === auth.user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const phasesCounter = ref({})
|
const phasesCounter = ref({})
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
const projects = (await useSupabaseSelect("projects")).filter(i => !i.archived)
|
const projects = (await useEntities("projects").select()).filter(i => !i.archived)
|
||||||
|
|
||||||
projects.forEach(project => {
|
projects.forEach(project => {
|
||||||
if(project.phases && project.phases.length > 0){
|
if(project.phases && project.phases.length > 0){
|
||||||
|
|||||||
@@ -1,23 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
const auth = useAuthStore()
|
||||||
const router = useRouter()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const tenant = ref({})
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
tenant.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="font-bold text-xl">Willkommen zurück {{profileStore.activeProfile.fullName}}</h1>
|
<h1 class="font-bold text-xl">Willkommen zurück {{auth.profile.full_name}}</h1>
|
||||||
<span v-if="tenant.id">bei {{tenant.name}}</span>
|
<span v-if="auth.activeTenant">bei {{auth.activeTenantData.name}}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ export const useCapacitor = () => {
|
|||||||
return deviceInfo.model.toLowerCase().includes('iphone')
|
return deviceInfo.model.toLowerCase().includes('iphone')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getIsNative = () => {
|
||||||
|
return Capacitor.isNativePlatform()
|
||||||
|
}
|
||||||
|
|
||||||
const getNetworkStatus = async () => {
|
const getNetworkStatus = async () => {
|
||||||
return await Network.getStatus()
|
return await Network.getStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {getPlatform, getDeviceInfo, getNetworkStatus, getIsPhone}
|
return {getPlatform, getDeviceInfo, getNetworkStatus, getIsPhone, getIsNative}
|
||||||
}
|
}
|
||||||
143
composables/useEntities.ts
Normal file
143
composables/useEntities.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
|
||||||
|
import { useDataStore } from "~/stores/data"
|
||||||
|
|
||||||
|
export const useEntities = (
|
||||||
|
relation: string,
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const dataStore = useDataStore()
|
||||||
|
const toast = useToast()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const dataType = dataStore.dataTypes[relation]
|
||||||
|
|
||||||
|
const select = async (
|
||||||
|
select: string = "*",
|
||||||
|
sortColumn: string | null = null,
|
||||||
|
ascending: boolean = false,
|
||||||
|
noArchivedFiltering: boolean = false
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api(`/api/resource/${relation}`, {
|
||||||
|
method: "GET",
|
||||||
|
params: {
|
||||||
|
select,
|
||||||
|
sort: sortColumn || undefined,
|
||||||
|
asc: ascending
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let data = res || []
|
||||||
|
|
||||||
|
if (dataType && dataType.isArchivable && !noArchivedFiltering) {
|
||||||
|
data = data.filter((i: any) => !i.archived)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectSpecial = async (
|
||||||
|
select: string = "*",
|
||||||
|
sortColumn: string | null = null,
|
||||||
|
ascending: boolean = false,
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api(`/api/resource-special/${relation}`, {
|
||||||
|
method: "GET",
|
||||||
|
params: {
|
||||||
|
select,
|
||||||
|
sort: sortColumn || undefined,
|
||||||
|
asc: ascending
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return res || []
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectSingle = async (
|
||||||
|
idToEq: string | number,
|
||||||
|
select: string = "*",
|
||||||
|
withInformation: boolean = false
|
||||||
|
) => {
|
||||||
|
if (!idToEq) return null
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api(withInformation ? `/api/resource/${relation}/${idToEq}/${withInformation}` : `/api/resource/${relation}/${idToEq}`, {
|
||||||
|
method: "GET",
|
||||||
|
params: { select }
|
||||||
|
})
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const create = async (
|
||||||
|
payload: Record<string, any>,
|
||||||
|
noRedirect: boolean = false
|
||||||
|
) => {
|
||||||
|
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api(`/api/resource/${relation}`, {
|
||||||
|
method: "POST",
|
||||||
|
body: payload
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.add({title: `${dataType.labelSingle} hinzugefügt`})
|
||||||
|
if(dataType.redirect && !noRedirect) {
|
||||||
|
if(dataType.isStandardEntity) {
|
||||||
|
await router.push(dataType.redirectToList ? `/standardEntity/${relation}` : `/standardEntity/${relation}/show/${res.id}`)
|
||||||
|
} else {
|
||||||
|
await router.push(dataType.redirectToList ? `/${relation}` : `/${relation}/show/${res.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//modal.close() TODO: Modal Close wenn in Modal
|
||||||
|
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = async (
|
||||||
|
id: string | number,
|
||||||
|
payload: Record<string, any>,
|
||||||
|
noRedirect: boolean = false
|
||||||
|
) => {
|
||||||
|
const res = await useNuxtApp().$api(`/api/resource/${relation}/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: payload
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.add({title: `${dataType.labelSingle} geändert`})
|
||||||
|
if(dataType.redirect && !noRedirect) {
|
||||||
|
if(dataType.isStandardEntity) {
|
||||||
|
await router.push(dataType.redirectToList ? `/standardEntity/${relation}` : `/standardEntity/${relation}/show/${res.id}`)
|
||||||
|
} else {
|
||||||
|
await router.push(dataType.redirectToList ? `/${relation}` : `/${relation}/show/${res.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//modal.close() TODO: Modal Close wenn in Modal
|
||||||
|
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft Delete = archived = true
|
||||||
|
*/
|
||||||
|
const archive = async (
|
||||||
|
id: string | number
|
||||||
|
) => {
|
||||||
|
const res = await useNuxtApp().$api(`/api/resource/${relation}/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: { archived: true }
|
||||||
|
})
|
||||||
|
navigateTo(dataType.isStandardEntity ? `/standardEntity/${relation}` : `/${relation}`)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {select, create, update, archive, selectSingle, selectSpecial}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
export const useError = (resourceType) => {
|
export const useErrorLogging = (resourceType) => {
|
||||||
const supabase = useSupabaseClient()
|
const supabase = useSupabaseClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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(*), contact(*)), 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}
|
|
||||||
}
|
|
||||||
129
composables/useFiles.ts
Normal file
129
composables/useFiles.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
export const useFiles = () => {
|
||||||
|
const supabase = useSupabaseClient()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
let bucket = "filesdev"
|
||||||
|
|
||||||
|
const uploadFiles = async (fileData, files,tags, upsert) => {
|
||||||
|
const uploadSingleFile = async (file) => {
|
||||||
|
//Create File Entry to Get ID for Folder
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
formData.append("file", file)
|
||||||
|
formData.append("meta", JSON.stringify(fileData))
|
||||||
|
|
||||||
|
const {fileReturn} = await useNuxtApp().$api("/api/files/upload",{
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
data = await useEntities("files").select("*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api("/api/files/presigned",{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
ids: data.map(i => i.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(res)
|
||||||
|
|
||||||
|
return res.files
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectSomeDocuments = async (documentIds, sortColumn = null, folder = null) => {
|
||||||
|
|
||||||
|
if(documentIds.length === 0) return []
|
||||||
|
const res = await useNuxtApp().$api("/api/files/presigned",{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
ids: documentIds
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(res)
|
||||||
|
|
||||||
|
return res.files
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectDocument = async (id) => {
|
||||||
|
let documentIds = [id]
|
||||||
|
if(documentIds.length === 0) return []
|
||||||
|
const res = await useNuxtApp().$api("/api/files/presigned",{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
ids: documentIds
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(res)
|
||||||
|
|
||||||
|
return res.files[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadFile = async (id?: string, ids?: string[], returnAsBlob: Boolean = false) => {
|
||||||
|
const url = id ? `/api/files/download/${id}` : `/api/files/download`
|
||||||
|
const body = ids ? { ids } : undefined
|
||||||
|
|
||||||
|
const res:any = await useNuxtApp().$api.raw(url, {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
responseType: "blob", // wichtig!
|
||||||
|
})
|
||||||
|
|
||||||
|
// Dateiname bestimmen
|
||||||
|
let filename = "download"
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
// Einzeldatei → nimm den letzten Teil des Pfads aus Content-Disposition
|
||||||
|
const contentDisposition = res.headers?.get("content-disposition")
|
||||||
|
if (contentDisposition) {
|
||||||
|
const match = contentDisposition.match(/filename="?([^"]+)"?/)
|
||||||
|
if (match) filename = match[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filename = "dateien.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direkt speichern
|
||||||
|
const blob = res._data as Blob
|
||||||
|
|
||||||
|
if(returnAsBlob) {
|
||||||
|
return blob
|
||||||
|
} else {
|
||||||
|
const link = document.createElement("a")
|
||||||
|
link.href = URL.createObjectURL(blob)
|
||||||
|
link.download = filename
|
||||||
|
link.click()
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument, downloadFile}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ export const useFunctions = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useNextNumber = async (numberRange) => {
|
const useNextNumber = async (numberRange) => {
|
||||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
/*const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||||
|
|
||||||
return (await axios({
|
return (await axios({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -35,7 +35,11 @@ export const useFunctions = () => {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${access_token}`
|
Authorization: `Bearer ${access_token}`
|
||||||
}
|
}
|
||||||
})).data.usedNumber
|
})).data.usedNumber*/
|
||||||
|
|
||||||
|
return (await useNuxtApp().$api(`/api/functions/usenextnumber/${numberRange}`,)).usedNumber
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCreateTicket = async (subject,message,url,source) => {
|
const useCreateTicket = async (subject,message,url,source) => {
|
||||||
@@ -80,9 +84,17 @@ export const useFunctions = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useCreatePDF = async (invoiceData,path) => {
|
const useCreatePDF = async (invoiceData,path) => {
|
||||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
//const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||||
|
|
||||||
const {data} = await axios({
|
const data = await useNuxtApp().$api(`/api/functions/createinvoicepdf`, {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
invoiceData: invoiceData,
|
||||||
|
backgroundPath: path,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*const {data} = await axios({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${baseURL}/functions/createpdf`,
|
url: `${baseURL}/functions/createpdf`,
|
||||||
data: {
|
data: {
|
||||||
@@ -93,7 +105,7 @@ export const useFunctions = () => {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${access_token}`
|
Authorization: `Bearer ${access_token}`
|
||||||
}
|
}
|
||||||
})
|
})*/
|
||||||
|
|
||||||
console.log(data)
|
console.log(data)
|
||||||
|
|
||||||
|
|||||||
9
composables/usePermission.ts
Normal file
9
composables/usePermission.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export function usePermission() {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
const has = (key: string) => {
|
||||||
|
return auth.hasPermission(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { has }
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ export const useSum = () => {
|
|||||||
const getIncomingInvoiceSum = (invoice) => {
|
const getIncomingInvoiceSum = (invoice) => {
|
||||||
let sum = 0
|
let sum = 0
|
||||||
invoice.accounts.forEach(account => {
|
invoice.accounts.forEach(account => {
|
||||||
console.log(account)
|
|
||||||
|
|
||||||
|
|
||||||
sum += account.amountTax
|
sum += account.amountTax
|
||||||
@@ -23,21 +22,9 @@ export const useSum = () => {
|
|||||||
let total19 = 0
|
let total19 = 0
|
||||||
let total7 = 0
|
let total7 = 0
|
||||||
|
|
||||||
/*let usedadvanceinvoices = []
|
|
||||||
if(createddocument.usedAdvanceInvoices.length > 0) {
|
|
||||||
console.log(createddocument)
|
|
||||||
console.log(createddocument.usedAdvanceInvoices)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log((await supabase.from("createddocuments").select().in("id", createddocument.usedAdvanceInvoices)))
|
|
||||||
|
|
||||||
usedadvanceinvoices = (await supabase.from("createddocuments").select().in("id", createddocument.usedAdvanceInvoices)).data
|
|
||||||
console.log(usedadvanceinvoices)
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
createddocument.rows.forEach(row => {
|
createddocument.rows.forEach(row => {
|
||||||
if(!['pagebreak','title','text'].includes(row.mode)){
|
if(!['pagebreak','title','text'].includes(row.mode)){
|
||||||
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
|
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import {useDataStore} from "~/stores/data.js";
|
|
||||||
|
|
||||||
|
|
||||||
export const useSupabaseSelect = async (relation,select = '*', sortColumn = null, ascending = true,noArchivedFiltering = false) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
let data = null
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
|
|
||||||
const dataType = dataStore.dataTypes[relation]
|
|
||||||
|
|
||||||
if(sortColumn !== null ) {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)
|
|
||||||
.order(sortColumn, {ascending: ascending})).data
|
|
||||||
} else {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)).data
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dataType && dataType.isArchivable && !noArchivedFiltering) {
|
|
||||||
data = data.filter(i => !i.archived)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSupabaseSelectSingle = async (relation,idToEq,select = '*' ) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
let data = null
|
|
||||||
|
|
||||||
|
|
||||||
if(idToEq !== null) {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)
|
|
||||||
.eq("id",idToEq)
|
|
||||||
.single()).data
|
|
||||||
} else {
|
|
||||||
data = (await supabase
|
|
||||||
.from(relation)
|
|
||||||
.select(select)
|
|
||||||
.eq("tenant", profileStore.currentTenant)
|
|
||||||
.single()).data
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
28
composables/useUsers.ts
Normal file
28
composables/useUsers.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
import { useDataStore } from "~/stores/data"
|
||||||
|
|
||||||
|
export const useUsers = (
|
||||||
|
id: string | number,
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const dataStore = useDataStore()
|
||||||
|
const toast = useToast()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const getProfile = async () => {
|
||||||
|
|
||||||
|
|
||||||
|
const res = await useNuxtApp().$api(`/api/profiles/${id}`, {
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return {getProfile}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -7,8 +7,7 @@ services:
|
|||||||
# networks:
|
# networks:
|
||||||
# - traefik
|
# - traefik
|
||||||
environment:
|
environment:
|
||||||
SUPABASE_URL: "https://uwppvcxflrcsibuzsbil.supabase.co"
|
NUXT_PUBLIC_API_BASE: "https://backend.fedeo.io"
|
||||||
SUPABASE_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo"
|
|
||||||
# labels:
|
# labels:
|
||||||
# - "traefik.enable=true"
|
# - "traefik.enable=true"
|
||||||
# - "traefik.docker.network=traefik"
|
# - "traefik.docker.network=traefik"
|
||||||
|
|||||||
30
error.vue
30
error.vue
@@ -7,11 +7,29 @@ const props = defineProps({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex justify-center h-100 w-100">
|
<UCard class="w-1/2 mx-auto mt-10 text-center">
|
||||||
<div class="mt-20">
|
<template #header>
|
||||||
<h1 class="text-5xl text-center mb-3">Da ist etwas schief gelaufen</h1>
|
<div class="text-center">
|
||||||
<UButton to="/">Zurück</UButton>
|
<span class="text-xl text-center font-bold">Es gab ein Problem - {{error.statusCode}}</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
class="w-3/4 mx-auto "
|
||||||
|
/>
|
||||||
|
<UAccordion
|
||||||
|
class="mt-5"
|
||||||
|
:items="[{
|
||||||
|
label: 'Fehlerbeschreibung',
|
||||||
|
icon: 'i-heroicons-information-circle',
|
||||||
|
defaultOpen: false,
|
||||||
|
content: error
|
||||||
|
}]"
|
||||||
|
>
|
||||||
|
|
||||||
|
</UAccordion>
|
||||||
|
<UButton to="/" class="mt-5">Zurück zur Startseite</UButton>
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</template>
|
||||||
@@ -485,7 +485,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = GMCGQ8KK2P;
|
DEVELOPMENT_TEAM = GMCGQ8KK2P;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
@@ -493,7 +493,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.8;
|
MARKETING_VERSION = 2.0;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = software.federspiel.fedeo;
|
PRODUCT_BUNDLE_IDENTIFIER = software.federspiel.fedeo;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -510,7 +510,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = GMCGQ8KK2P;
|
DEVELOPMENT_TEAM = GMCGQ8KK2P;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
@@ -518,7 +518,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.8;
|
MARKETING_VERSION = 2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = software.federspiel.fedeo;
|
PRODUCT_BUNDLE_IDENTIFIER = software.federspiel.fedeo;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ def capacitor_pods
|
|||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
|
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
|
||||||
pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
|
pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
|
||||||
|
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
|
||||||
pod 'CapacitorPluginSafeArea', :path => '../../node_modules/capacitor-plugin-safe-area'
|
pod 'CapacitorPluginSafeArea', :path => '../../node_modules/capacitor-plugin-safe-area'
|
||||||
pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'
|
pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ PODS:
|
|||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorPluginSafeArea (4.0.0):
|
- CapacitorPluginSafeArea (4.0.0):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
|
- CapacitorPreferences (6.0.3):
|
||||||
|
- Capacitor
|
||||||
- CordovaPluginsStatic (7.1.0):
|
- CordovaPluginsStatic (7.1.0):
|
||||||
- CapacitorCordova
|
- CapacitorCordova
|
||||||
- OneSignalXCFramework (= 5.2.10)
|
- OneSignalXCFramework (= 5.2.10)
|
||||||
@@ -64,6 +66,7 @@ DEPENDENCIES:
|
|||||||
- "CapacitorDevice (from `../../node_modules/@capacitor/device`)"
|
- "CapacitorDevice (from `../../node_modules/@capacitor/device`)"
|
||||||
- "CapacitorNetwork (from `../../node_modules/@capacitor/network`)"
|
- "CapacitorNetwork (from `../../node_modules/@capacitor/network`)"
|
||||||
- CapacitorPluginSafeArea (from `../../node_modules/capacitor-plugin-safe-area`)
|
- CapacitorPluginSafeArea (from `../../node_modules/capacitor-plugin-safe-area`)
|
||||||
|
- "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)"
|
||||||
- CordovaPluginsStatic (from `../capacitor-cordova-ios-plugins`)
|
- CordovaPluginsStatic (from `../capacitor-cordova-ios-plugins`)
|
||||||
- OneSignalXCFramework (< 6.0, >= 5.0)
|
- OneSignalXCFramework (< 6.0, >= 5.0)
|
||||||
|
|
||||||
@@ -82,6 +85,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../../node_modules/@capacitor/network"
|
:path: "../../node_modules/@capacitor/network"
|
||||||
CapacitorPluginSafeArea:
|
CapacitorPluginSafeArea:
|
||||||
:path: "../../node_modules/capacitor-plugin-safe-area"
|
:path: "../../node_modules/capacitor-plugin-safe-area"
|
||||||
|
CapacitorPreferences:
|
||||||
|
:path: "../../node_modules/@capacitor/preferences"
|
||||||
CordovaPluginsStatic:
|
CordovaPluginsStatic:
|
||||||
:path: "../capacitor-cordova-ios-plugins"
|
:path: "../capacitor-cordova-ios-plugins"
|
||||||
|
|
||||||
@@ -91,9 +96,10 @@ SPEC CHECKSUMS:
|
|||||||
CapacitorDevice: 069faf433b3a99c3d5f0e500fbe634f60a8c6a84
|
CapacitorDevice: 069faf433b3a99c3d5f0e500fbe634f60a8c6a84
|
||||||
CapacitorNetwork: 30c2e78a0ed32530656cb426c8ee6c2caec10dbf
|
CapacitorNetwork: 30c2e78a0ed32530656cb426c8ee6c2caec10dbf
|
||||||
CapacitorPluginSafeArea: 22031c3436269ca80fac90ec2c94bc7c1e59a81d
|
CapacitorPluginSafeArea: 22031c3436269ca80fac90ec2c94bc7c1e59a81d
|
||||||
|
CapacitorPreferences: f3eadae2369ac3ab8e21743a2959145b0d1286a3
|
||||||
CordovaPluginsStatic: f722d4ff434f50099581e690d579b7c108f490e6
|
CordovaPluginsStatic: f722d4ff434f50099581e690d579b7c108f490e6
|
||||||
OneSignalXCFramework: 1a3b28dfbff23aabce585796d23c1bef37772774
|
OneSignalXCFramework: 1a3b28dfbff23aabce585796d23c1bef37772774
|
||||||
|
|
||||||
PODFILE CHECKSUM: ccfbce7f13cfefd953204fe26b280d6431731aa5
|
PODFILE CHECKSUM: d76fcd3d35c3f8c3708303de70ef45a76cc6e2b5
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -3,19 +3,18 @@
|
|||||||
|
|
||||||
import MainNav from "~/components/MainNav.vue";
|
import MainNav from "~/components/MainNav.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useProfileStore} from "~/stores/profile.js";
|
|
||||||
import {useCapacitor} from "../composables/useCapacitor.js";
|
import {useCapacitor} from "../composables/useCapacitor.js";
|
||||||
import GlobalMessages from "~/components/GlobalMessages.vue";
|
import GlobalMessages from "~/components/GlobalMessages.vue";
|
||||||
|
import TenantDropdown from "~/components/TenantDropdown.vue";
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const { isHelpSlideoverOpen } = useDashboard()
|
const { isHelpSlideoverOpen } = useDashboard()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
|
||||||
const month = dayjs().format("MM")
|
const month = dayjs().format("MM")
|
||||||
|
|
||||||
@@ -65,8 +64,8 @@ const actions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
const groups = computed(() =>
|
const groups = computed(() => [
|
||||||
[{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
commands: actions
|
commands: actions
|
||||||
},{
|
},{
|
||||||
@@ -99,7 +98,8 @@ const groups = computed(() =>
|
|||||||
commands: dataStore.projects.map(item => { return {id: item.id, label: item.name, to: `/projects/show/${item.id}`}})
|
commands: dataStore.projects.map(item => { return {id: item.id, label: item.name, to: `/projects/show/${item.id}`}})
|
||||||
}
|
}
|
||||||
].filter(Boolean))
|
].filter(Boolean))
|
||||||
const footerLinks = [/*{
|
const footerLinks = [
|
||||||
|
/*{
|
||||||
label: 'Invite people',
|
label: 'Invite people',
|
||||||
icon: 'i-heroicons-plus',
|
icon: 'i-heroicons-plus',
|
||||||
to: '/settings/members'
|
to: '/settings/members'
|
||||||
@@ -112,21 +112,127 @@ const footerLinks = [/*{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UDashboardLayout class="safearea" v-if="profileStore.loaded">
|
<div v-if="!auth.loading">
|
||||||
|
<div v-if="auth.activeTenantData?.locked === 'maintenance_tenant'">
|
||||||
|
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
|
<UCard class="max-w-lg text-center p-10">
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo_Hell_Weihnachten.png"
|
||||||
|
dark="/Logo_Dunkel_Weihnachten.png"
|
||||||
|
class=" mx-auto my-10"
|
||||||
|
v-if="month === '12'"
|
||||||
|
/>
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
class="mx-auto my-10"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
<div class="flex justify-center mb-6">
|
||||||
|
<UIcon name="i-heroicons-exclamation-triangle-solid" class="w-16 h-16 text-yellow-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Wartungsarbeiten
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||||
|
Dieser FEDEO Mandant wird derzeit gewartet. Bitte versuche es in einigen Minuten erneut oder verwende einen anderen Mandanten.
|
||||||
|
</p>
|
||||||
|
<div class="mx-auto text-left flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||||
|
{{tenant.name}}
|
||||||
|
<UButton
|
||||||
|
:disabled="tenant.locked"
|
||||||
|
@click="auth.switchTenant(tenant.id)"
|
||||||
|
>Wählen</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</UContainer>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="auth.activeTenantData?.locked === 'maintenance'">
|
||||||
|
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
|
<UCard class="max-w-lg text-center p-10">
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo_Hell_Weihnachten.png"
|
||||||
|
dark="/Logo_Dunkel_Weihnachten.png"
|
||||||
|
class=" mx-auto my-10"
|
||||||
|
v-if="month === '12'"
|
||||||
|
/>
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
class="mx-auto my-10"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
<div class="flex justify-center mb-6">
|
||||||
|
<UIcon name="i-heroicons-exclamation-triangle-solid" class="w-16 h-16 text-yellow-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Wartungsarbeiten
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||||
|
FEDEO wird derzeit gewartet. Bitte versuche es in einigen Minuten erneut.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</UContainer>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="auth.activeTenantData?.locked === 'no_subscription'">
|
||||||
|
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
|
<UCard class="max-w-lg text-center p-10">
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo_Hell_Weihnachten.png"
|
||||||
|
dark="/Logo_Dunkel_Weihnachten.png"
|
||||||
|
class=" mx-auto my-10"
|
||||||
|
v-if="month === '12'"
|
||||||
|
/>
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
class="mx-auto my-10"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
<div class="flex justify-center mb-6">
|
||||||
|
<UIcon name="i-heroicons-credit-card" class="w-16 h-16 text-red-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Kein Aktives Abonnement für diesen Mandant.
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||||
|
Bitte wenden Sie sich an den FEDEO Support um ein Abonnement zu erhalten oder verwenden Sie einen anderen Mandanten.
|
||||||
|
</p>
|
||||||
|
<div class="mx-auto text-left flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||||
|
{{tenant.name}}
|
||||||
|
<UButton
|
||||||
|
:disabled="tenant.locked"
|
||||||
|
@click="auth.switchTenant(tenant.id)"
|
||||||
|
>Wählen</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</UContainer>
|
||||||
|
</div>
|
||||||
|
<UDashboardLayout class="safearea" v-else >
|
||||||
<UDashboardPanel :width="250" :resizable="{ min: 200, max: 300 }" collapsible>
|
<UDashboardPanel :width="250" :resizable="{ min: 200, max: 300 }" collapsible>
|
||||||
<UDashboardNavbar style="margin-top: env(safe-area-inset-top, 10px) !important;" :class="['!border-transparent']" :ui="{ left: 'flex-1' }">
|
<UDashboardNavbar style="margin-top: env(safe-area-inset-top, 10px) !important;" :class="['!border-transparent']" :ui="{ left: 'flex-1' }">
|
||||||
<template #left>
|
<template #left>
|
||||||
<ProfileDropdown class="w-full" />
|
<TenantDropdown class="w-full" />
|
||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
|
|
||||||
<UDashboardSidebar id="sidebar">
|
<UDashboardSidebar id="sidebar">
|
||||||
<template #header>
|
<!-- <template #header>
|
||||||
<UDashboardSearchButton label="Suche..."/>
|
<UDashboardSearchButton label="Suche..."/>
|
||||||
</template>
|
</template>-->
|
||||||
|
|
||||||
|
<!--
|
||||||
<GlobalMessages/>
|
<GlobalMessages/>
|
||||||
|
-->
|
||||||
|
|
||||||
<MainNav/>
|
<MainNav/>
|
||||||
|
|
||||||
@@ -153,15 +259,16 @@ const footerLinks = [/*{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ~/components/HelpSlideover.vue -->
|
|
||||||
<HelpSlideover/>
|
<HelpSlideover/>
|
||||||
<!-- ~/components/NotificationsSlideover.vue -->
|
|
||||||
<NotificationsSlideover />
|
<!--<NotificationsSlideover />
|
||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<LazyUDashboardSearch :groups="groups" hide-color-mode/>
|
<LazyUDashboardSearch :groups="groups" hide-color-mode/>
|
||||||
</ClientOnly>
|
</ClientOnly>-->
|
||||||
</UDashboardLayout>
|
</UDashboardLayout>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex flex-col"
|
class="flex flex-col"
|
||||||
@@ -178,12 +285,25 @@ const footerLinks = [/*{
|
|||||||
class="w-1/3 mx-auto my-10"
|
class="w-1/3 mx-auto my-10"
|
||||||
v-else
|
v-else
|
||||||
/>
|
/>
|
||||||
<div v-if="profileStore.showProfileSelection">
|
|
||||||
<ProfileSelection/>
|
<div v-if="!auth.activeTenant" class="w-1/2 mx-auto text-center">
|
||||||
|
<!-- Tenant Selection -->
|
||||||
|
<h3 class="text-center font-bold text-2xl mb-5">Kein Aktiver Mandant. Bitte wählen Sie ein Mandant.</h3>
|
||||||
|
<div class="mx-auto w-1/2 flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||||
|
<span class="text-left">{{tenant.name}}</span>
|
||||||
|
<UButton
|
||||||
|
@click="auth.switchTenant(tenant.id)"
|
||||||
|
>Wählen</UButton>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
variant="outline"
|
||||||
|
color="rose"
|
||||||
|
@click="auth.logout()"
|
||||||
|
>Abmelden</UButton>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
|
||||||
|
|
||||||
<UProgress animation="carousel" class="w-3/4 mx-auto mt-10" />
|
<UProgress animation="carousel" class="w-3/4 mx-auto mt-10" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -192,7 +312,5 @@ const footerLinks = [/*{
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.testclass {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -13,8 +13,9 @@ const { isHelpSlideoverOpen } = useDashboard()
|
|||||||
const supabase = useSupabaseClient()
|
const supabase = useSupabaseClient()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
//profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||||
|
|
||||||
const month = dayjs().format("MM")
|
const month = dayjs().format("MM")
|
||||||
|
|
||||||
@@ -64,40 +65,6 @@ const actions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
const groups = computed(() =>
|
|
||||||
[{
|
|
||||||
key: 'actions',
|
|
||||||
commands: actions
|
|
||||||
},{
|
|
||||||
key: "customers",
|
|
||||||
label: "Kunden",
|
|
||||||
commands: dataStore.customers.map(item => { return {id: item.id, label: item.name, to: `/customers/show/${item.id}`}})
|
|
||||||
},{
|
|
||||||
key: "vendors",
|
|
||||||
label: "Lieferanten",
|
|
||||||
commands: dataStore.vendors.map(item => { return {id: item.id, label: item.name, to: `/vendors/show/${item.id}`}})
|
|
||||||
},{
|
|
||||||
key: "contacts",
|
|
||||||
label: "Ansprechpartner",
|
|
||||||
commands: dataStore.contacts.map(item => { return {id: item.id, label: item.fullName, to: `/contacts/show/${item.id}`}})
|
|
||||||
},{
|
|
||||||
key: "products",
|
|
||||||
label: "Artikel",
|
|
||||||
commands: dataStore.products.map(item => { return {id: item.id, label: item.name, to: `/products/show/${item.id}`}})
|
|
||||||
},{
|
|
||||||
key: "tasks",
|
|
||||||
label: "Aufgaben",
|
|
||||||
commands: dataStore.tasks.map(item => { return {id: item.id, label: item.name, to: `/tasks/show/${item.id}`}})
|
|
||||||
},{
|
|
||||||
key: "plants",
|
|
||||||
label: "Objekte",
|
|
||||||
commands: dataStore.plants.map(item => { return {id: item.id, label: item.name, to: `/plants/show/${item.id}`}})
|
|
||||||
},{
|
|
||||||
key: "projects",
|
|
||||||
label: "Projekte",
|
|
||||||
commands: dataStore.projects.map(item => { return {id: item.id, label: item.name, to: `/projects/show/${item.id}`}})
|
|
||||||
}
|
|
||||||
].filter(Boolean))
|
|
||||||
const footerLinks = [/*{
|
const footerLinks = [/*{
|
||||||
label: 'Invite people',
|
label: 'Invite people',
|
||||||
icon: 'i-heroicons-plus',
|
icon: 'i-heroicons-plus',
|
||||||
@@ -111,36 +78,112 @@ const footerLinks = [/*{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UDashboardLayout class="safearea" v-if="profileStore.loaded">
|
<div v-if="!auth.loading">
|
||||||
|
<div v-if="auth.activeTenantData?.locked === 'maintenance_tenant'">
|
||||||
<UDashboardPanel :width="250" :resizable="{ min: 200, max: 300 }" collapsible>
|
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
<UDashboardNavbar style="margin-top: env(safe-area-inset-top, 10px) !important;" :class="['!border-transparent']" :ui="{ left: 'flex-1' }">
|
<UCard class="max-w-lg text-center p-10">
|
||||||
<template #left>
|
<UColorModeImage
|
||||||
<ProfileDropdown class="w-full" />
|
light="/Logo_Hell_Weihnachten.png"
|
||||||
</template>
|
dark="/Logo_Dunkel_Weihnachten.png"
|
||||||
</UDashboardNavbar>
|
class=" mx-auto my-10"
|
||||||
|
v-if="month === '12'"
|
||||||
<UDashboardSidebar id="sidebar">
|
/>
|
||||||
<template #header>
|
<UColorModeImage
|
||||||
<UDashboardSearchButton v-if="!useCapacitor().getIsPhone()" label="Suche..."/>
|
light="/Logo.png"
|
||||||
</template>
|
dark="/Logo_Dark.png"
|
||||||
|
class="mx-auto my-10"
|
||||||
<MainNav/>
|
v-else
|
||||||
|
/>
|
||||||
<div class="flex-1" />
|
<div class="flex justify-center mb-6">
|
||||||
|
<UIcon name="i-heroicons-exclamation-triangle-solid" class="w-16 h-16 text-yellow-500" />
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="flex flex-col w-full">
|
|
||||||
|
|
||||||
<UDashboardSidebarLinks :links="footerLinks" />
|
|
||||||
<UDivider class="sticky bottom-0" />
|
|
||||||
<UserDropdown style="margin-bottom: env(safe-area-inset-bottom, 10px) !important;"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
</UDashboardSidebar>
|
Wartungsarbeiten
|
||||||
</UDashboardPanel>
|
</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||||
|
Dieser FEDEO Mandant wird derzeit gewartet. Bitte versuche es in einigen Minuten erneut oder verwende einen anderen Mandanten.
|
||||||
|
</p>
|
||||||
|
<div class="mx-auto text-left flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||||
|
{{tenant.name}}
|
||||||
|
<UButton
|
||||||
|
:disabled="tenant.locked"
|
||||||
|
@click="auth.switchTenant(tenant.id)"
|
||||||
|
>Wählen</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</UContainer>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="auth.activeTenantData?.locked === 'maintenance'">
|
||||||
|
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
|
<UCard class="max-w-lg text-center p-10">
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo_Hell_Weihnachten.png"
|
||||||
|
dark="/Logo_Dunkel_Weihnachten.png"
|
||||||
|
class=" mx-auto my-10"
|
||||||
|
v-if="month === '12'"
|
||||||
|
/>
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
class="mx-auto my-10"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
<div class="flex justify-center mb-6">
|
||||||
|
<UIcon name="i-heroicons-exclamation-triangle-solid" class="w-16 h-16 text-yellow-500" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Wartungsarbeiten
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||||
|
FEDEO wird derzeit gewartet. Bitte versuche es in einigen Minuten erneut.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</UContainer>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="auth.activeTenantData?.locked === 'no_subscription'">
|
||||||
|
<UContainer class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
|
<UCard class="max-w-lg text-center p-10">
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo_Hell_Weihnachten.png"
|
||||||
|
dark="/Logo_Dunkel_Weihnachten.png"
|
||||||
|
class=" mx-auto my-10"
|
||||||
|
v-if="month === '12'"
|
||||||
|
/>
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
class="mx-auto my-10"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
<div class="flex justify-center mb-6">
|
||||||
|
<UIcon name="i-heroicons-credit-card" class="w-16 h-16 text-red-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Kein Aktives Abonnement für diesen Mandant.
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-300 mb-8">
|
||||||
|
Bitte wenden Sie sich an den FEDEO Support um ein Abonnement zu erhalten oder verwenden Sie einen anderen Mandanten.
|
||||||
|
</p>
|
||||||
|
<div class="mx-auto text-left flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||||
|
{{tenant.name}}
|
||||||
|
<UButton
|
||||||
|
:disabled="tenant.locked"
|
||||||
|
@click="auth.switchTenant(tenant.id)"
|
||||||
|
>Wählen</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</UContainer>
|
||||||
|
</div>
|
||||||
|
<UDashboardLayout class="safearea" v-else>
|
||||||
|
|
||||||
<UDashboardPage style="height: 90vh">
|
<UDashboardPage style="height: 90vh">
|
||||||
<UDashboardPanel grow>
|
<UDashboardPanel grow>
|
||||||
@@ -188,10 +231,10 @@ const footerLinks = [/*{
|
|||||||
<!-- ~/components/NotificationsSlideover.vue -->
|
<!-- ~/components/NotificationsSlideover.vue -->
|
||||||
<NotificationsSlideover />
|
<NotificationsSlideover />
|
||||||
|
|
||||||
<ClientOnly>
|
|
||||||
<LazyUDashboardSearch :groups="groups" hide-color-mode/>
|
|
||||||
</ClientOnly>
|
|
||||||
</UDashboardLayout>
|
</UDashboardLayout>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex flex-col"
|
class="flex flex-col"
|
||||||
@@ -208,15 +251,26 @@ const footerLinks = [/*{
|
|||||||
class="w-1/3 mx-auto my-10"
|
class="w-1/3 mx-auto my-10"
|
||||||
v-else
|
v-else
|
||||||
/>
|
/>
|
||||||
<div v-if="dataStore.showProfileSelection">
|
<div v-if="!auth.activeTenant" class="w-full mx-auto text-center">
|
||||||
<ProfileSelection/>
|
<!-- Tenant Selection -->
|
||||||
|
<h3 class="text-center font-bold text-2xl mb-5">Kein Aktiver Mandant. Bitte wählen Sie ein Mandant.</h3>
|
||||||
|
<div class="mx-auto w-5/6 flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||||
|
<span class="text-left">{{tenant.name}}</span>
|
||||||
|
<UButton
|
||||||
|
@click="auth.switchTenant(tenant.id)"
|
||||||
|
>Wählen</UButton>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
variant="outline"
|
||||||
|
color="rose"
|
||||||
|
@click="auth.logout()"
|
||||||
|
>Abmelden</UButton>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
|
||||||
|
|
||||||
<UProgress animation="carousel" class="w-3/4 mx-auto mt-10" />
|
<UProgress animation="carousel" class="w-3/4 mx-auto mt-10" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
17
middleware/auth.global.ts
Normal file
17
middleware/auth.global.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Wenn nicht eingeloggt → auf /login (außer er will schon dahin)
|
||||||
|
if (!auth.user && !["/login", "/password-reset"].includes(to.path)) {
|
||||||
|
return navigateTo("/login")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn eingeloggt → von /login auf /dashboard umleiten
|
||||||
|
if (auth.user && !auth.user?.must_change_password && to.path === "/login") {
|
||||||
|
return navigateTo("/")
|
||||||
|
} else if(auth.user && auth.user.must_change_password && to.path !== "/password-change") {
|
||||||
|
return navigateTo("/password-change")
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export default defineNuxtRouteMiddleware((to, _from) => {
|
|
||||||
const user = useSupabaseUser()
|
|
||||||
const router = useRouter()
|
|
||||||
if (!user.value) {
|
|
||||||
//useCookie('redirect', { path: '/' }).value = to.fullPath
|
|
||||||
return router.push("/login")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -3,7 +3,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
|||||||
|
|
||||||
console.log(await useCapacitor().getIsPhone())
|
console.log(await useCapacitor().getIsPhone())
|
||||||
|
|
||||||
if(await useCapacitor().getIsPhone()) {
|
if(await useCapacitor().getIsPhone() && _from.path !== '/mobile') {
|
||||||
return router.push('/mobile')
|
return router.push('/mobile')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -25,7 +25,7 @@ export default defineNuxtConfig({
|
|||||||
transpile: ['@vuepic/vue-datepicker']
|
transpile: ['@vuepic/vue-datepicker']
|
||||||
},
|
},
|
||||||
|
|
||||||
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxt/content', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', 'nuxt-tiptap-editor', '@nuxtjs/leaflet'],
|
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', 'nuxt-tiptap-editor', '@nuxtjs/leaflet'],
|
||||||
|
|
||||||
routeRules: {
|
routeRules: {
|
||||||
'/printing': {ssr: false}
|
'/printing': {ssr: false}
|
||||||
@@ -34,7 +34,8 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
supabase: {
|
supabase: {
|
||||||
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo",
|
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo",
|
||||||
url: "https://uwppvcxflrcsibuzsbil.supabase.co"
|
url: "https://uwppvcxflrcsibuzsbil.supabase.co",
|
||||||
|
redirect:false
|
||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
@@ -56,5 +57,13 @@ export default defineNuxtConfig({
|
|||||||
prefix: "Tiptap"
|
prefix: "Tiptap"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
runtimeConfig: {
|
||||||
|
|
||||||
|
public: {
|
||||||
|
apiBase: '',
|
||||||
|
pdfLicense: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
compatibilityDate: '2024-12-18'
|
compatibilityDate: '2024-12-18'
|
||||||
})
|
})
|
||||||
0
package-lock.json
generated
0
package-lock.json
generated
12
package.json
12
package.json
@@ -10,21 +10,21 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@capacitor/cli": "^7.0.0",
|
||||||
"@nuxtjs/leaflet": "^1.2.3",
|
"@nuxtjs/leaflet": "^1.2.3",
|
||||||
"@nuxtjs/supabase": "^1.1.4",
|
"@nuxtjs/supabase": "^1.1.4",
|
||||||
"nuxt": "^3.14.1592",
|
"nuxt": "^3.14.1592",
|
||||||
"nuxt-tiptap-editor": "^1.2.0",
|
"nuxt-tiptap-editor": "^1.2.0",
|
||||||
"vite-plugin-pwa": "^0.17.3",
|
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^7.1.0",
|
"@capacitor/android": "^7.0.0",
|
||||||
"@capacitor/cli": "^7.1.0",
|
"@capacitor/core": "^7.0.0",
|
||||||
"@capacitor/core": "^7.1.0",
|
|
||||||
"@capacitor/device": "^7.0.0",
|
"@capacitor/device": "^7.0.0",
|
||||||
"@capacitor/ios": "^7.1.0",
|
"@capacitor/ios": "^7.0.0",
|
||||||
"@capacitor/network": "^7.0.0",
|
"@capacitor/network": "^7.0.0",
|
||||||
|
"@capacitor/preferences": "^7.0.0",
|
||||||
"@fullcalendar/core": "^6.1.10",
|
"@fullcalendar/core": "^6.1.10",
|
||||||
"@fullcalendar/daygrid": "^6.1.10",
|
"@fullcalendar/daygrid": "^6.1.10",
|
||||||
"@fullcalendar/interaction": "^6.1.10",
|
"@fullcalendar/interaction": "^6.1.10",
|
||||||
@@ -34,7 +34,6 @@
|
|||||||
"@fullcalendar/timegrid": "^6.1.10",
|
"@fullcalendar/timegrid": "^6.1.10",
|
||||||
"@fullcalendar/vue3": "^6.1.10",
|
"@fullcalendar/vue3": "^6.1.10",
|
||||||
"@iconify/json": "^2.2.171",
|
"@iconify/json": "^2.2.171",
|
||||||
"@nuxt/content": "^2.9.0",
|
|
||||||
"@nuxt/ui-pro": "^1.6.0",
|
"@nuxt/ui-pro": "^1.6.0",
|
||||||
"@nuxtjs/fontaine": "^0.4.1",
|
"@nuxtjs/fontaine": "^0.4.1",
|
||||||
"@nuxtjs/google-fonts": "^3.1.0",
|
"@nuxtjs/google-fonts": "^3.1.0",
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"@tiptap/vue-3": "^2.1.15",
|
"@tiptap/vue-3": "^2.1.15",
|
||||||
"@vicons/ionicons5": "^0.12.0",
|
"@vicons/ionicons5": "^0.12.0",
|
||||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||||
|
"@vue-pdf-viewer/viewer": "^3.0.1",
|
||||||
"@vuepic/vue-datepicker": "^7.4.0",
|
"@vuepic/vue-datepicker": "^7.4.0",
|
||||||
"@zip.js/zip.js": "^2.7.32",
|
"@zip.js/zip.js": "^2.7.32",
|
||||||
"array-sort": "^1.0.0",
|
"array-sort": "^1.0.0",
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'/': () => {
|
'/': () => {
|
||||||
@@ -15,16 +10,14 @@ defineShortcuts({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const tempStore = useTempStore()
|
const tempStore = useTempStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const dataLoaded = ref(false)
|
const dataLoaded = ref(false)
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
items.value = (await supabase.from("accounts").select("*").order("number", {ascending:true})).data
|
items.value = await useEntities("accounts").selectSpecial()
|
||||||
|
|
||||||
items.value = await Promise.all(items.value.map(async (i) => {
|
items.value = await Promise.all(items.value.map(async (i) => {
|
||||||
let renderedAllocationsTemp = await renderedAllocations(i.id)
|
let renderedAllocationsTemp = await renderedAllocations(i.id)
|
||||||
@@ -44,8 +37,8 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
const renderedAllocations = async (account) => {
|
const renderedAllocations = async (account) => {
|
||||||
|
|
||||||
let statementallocations = (await supabase.from("statementallocations").select("*, bs_id(*)").eq("account", account).eq("tenant",profileStore.currentTenant).order("created_at",{ascending: true})).data
|
let statementallocations = (await useEntities("statementallocations").select("*, bs_id(*)")).filter(i => i.account === account)
|
||||||
let incominginvoices = (await useSupabaseSelect("incominginvoices", "*, vendor(*)")).filter(i => i.accounts.find(x => x.account === account))
|
let incominginvoices = (await useEntities("incominginvoices").select("*, vendor(*)")).filter(i => i.accounts.find(x => x.account === account))
|
||||||
|
|
||||||
let tempstatementallocations = statementallocations.map(i => {
|
let tempstatementallocations = statementallocations.map(i => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const itemInfo = ref(null)
|
const itemInfo = ref(null)
|
||||||
const statementallocations = ref([])
|
const statementallocations = ref([])
|
||||||
const incominginvoices = ref([])
|
const incominginvoices = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
itemInfo.value = (await supabase.from("accounts").select("*").eq("id",route.params.id).single()).data
|
itemInfo.value = (await useEntities("accounts").selectSpecial("*")).find(i => i.id === Number(route.params.id))
|
||||||
statementallocations.value = (await supabase.from("statementallocations").select("*, bs_id(*)").eq("account", route.params.id).eq("tenant",profileStore.currentTenant).order("created_at",{ascending: true})).data
|
statementallocations.value = (await useEntities("statementallocations").select("*, bs_id(*)")).filter(i => i.account === Number(route.params.id))
|
||||||
incominginvoices.value = (await useSupabaseSelect("incominginvoices", "*, vendor(*)")).filter(i => i.accounts.find(x => x.account == route.params.id))
|
incominginvoices.value = (await useEntities("incominginvoices").select("*, vendor(*)")).filter(i => i.accounts.find(x => x.account === Number(route.params.id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const route = useRoute()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
const searchString = ref("")
|
|
||||||
const showAssigned = ref(false)
|
|
||||||
const selectedAccount = ref(0)
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
let statements = dataStore.bankStatements
|
|
||||||
|
|
||||||
if(!showAssigned.value) {
|
|
||||||
statements = statements.filter(statement => !statement.customerInvoice || !statement.vendorInvoice)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(selectedAccount.value !== 0) {
|
|
||||||
statements = statements.filter(statement => statement.account === selectedAccount.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(searchString.value.length > 0) {
|
|
||||||
statements = statements.filter(item => {
|
|
||||||
return Object.values(item).some((value) => {
|
|
||||||
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return statements
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const showStatementSlideover = ref(false)
|
|
||||||
const selectedStatement = ref({})
|
|
||||||
|
|
||||||
const selectStatement = (statement) => {
|
|
||||||
selectedStatement.value = statement
|
|
||||||
showStatementSlideover.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const statementColumns = [
|
|
||||||
{
|
|
||||||
key:"amount",
|
|
||||||
label: "Betrag"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"date",
|
|
||||||
label: "Datum",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "credName",
|
|
||||||
label: "Empfänger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "debName",
|
|
||||||
label: "Sender"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "text",
|
|
||||||
label: "Verwendungszweck"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<USlideover
|
|
||||||
v-model="showStatementSlideover"
|
|
||||||
>
|
|
||||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<DevOnly>
|
|
||||||
{{selectedStatement}}
|
|
||||||
</DevOnly>
|
|
||||||
</UCard>
|
|
||||||
|
|
||||||
</USlideover>
|
|
||||||
|
|
||||||
<InputGroup :gap="2">
|
|
||||||
<UInput
|
|
||||||
v-model="searchString"
|
|
||||||
placeholder="Suche..."
|
|
||||||
/>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.bankAccounts.filter(account => account.used)"
|
|
||||||
v-model="selectedAccount"
|
|
||||||
option-attribute="iban"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.bankAccounts.find(account => account.id === selectedAccount) ? dataStore.bankAccounts.find(account => account.id === selectedAccount).iban : "Kontoauswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
<UCheckbox
|
|
||||||
v-model="showAssigned"
|
|
||||||
label="Zugeordnete Anzeigen"
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
<UTable
|
|
||||||
:rows="filteredRows"
|
|
||||||
:columns="statementColumns"
|
|
||||||
@select="selectStatement"
|
|
||||||
>
|
|
||||||
<template #amount-data="{row}">
|
|
||||||
<span
|
|
||||||
v-if="row.amount >= 0"
|
|
||||||
class="text-primary-500"
|
|
||||||
>
|
|
||||||
{{row.amount.toFixed(2) + " €"}}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="row.amount < 0"
|
|
||||||
class="text-rose-600"
|
|
||||||
>
|
|
||||||
{{row.amount.toFixed(2) + " €"}}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template #date-data="{row}">
|
|
||||||
{{dayjs(row.date).format("DD.MM.YY")}}
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'/': () => {
|
'/': () => {
|
||||||
//console.log(searchinput)
|
//console.log(searchinput)
|
||||||
@@ -14,17 +10,15 @@ defineShortcuts({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const tempStore = useTempStore()
|
const tempStore = useTempStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const bankstatements = ref([])
|
const bankstatements = ref([])
|
||||||
const bankaccounts = ref([])
|
const bankaccounts = ref([])
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
bankstatements.value = (await supabase.from("bankstatements").select("*, statementallocations(*)").is("archived",false).eq('tenant', profileStore.currentTenant).order("date", {ascending:false})).data
|
bankstatements.value = (await useEntities("bankstatements").select("*, statementallocations(*)", "date", false))
|
||||||
bankaccounts.value = await useSupabaseSelect("bankaccounts")
|
bankaccounts.value = await useEntities("bankaccounts").select()
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateColumns = [
|
const templateColumns = [
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {filter} from "vuedraggable/dist/vuedraggable.common.js";
|
import {filter} from "vuedraggable/dist/vuedraggable.common.js";
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'backspace': () => {
|
'backspace': () => {
|
||||||
@@ -18,7 +16,6 @@ const profileStore = useProfileStore()
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const mode = ref(route.params.mode || "show")
|
const mode = ref(route.params.mode || "show")
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const itemInfo = ref({statementallocations:[]})
|
const itemInfo = ref({statementallocations:[]})
|
||||||
const oldItemInfo = ref({})
|
const oldItemInfo = ref({})
|
||||||
@@ -38,21 +35,22 @@ const ownaccounts = ref([])
|
|||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
|
loading.value = true
|
||||||
if(route.params.id) {
|
if(route.params.id) {
|
||||||
itemInfo.value = (await supabase.from("bankstatements").select("*, statementallocations(*, cd_id(*), ii_id(*))").eq("id",route.params.id).single()).data //dataStore.bankstatements.find(i => i.id === Number(route.params.id))
|
itemInfo.value = await useEntities("bankstatements").selectSingle(route.params.id,"*, statementallocations(*, cd_id(*), ii_id(*))", undefined, undefined, true)
|
||||||
}
|
}
|
||||||
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
||||||
|
|
||||||
manualAllocationSum.value = calculateOpenSum.value
|
manualAllocationSum.value = calculateOpenSum.value
|
||||||
|
|
||||||
createddocuments.value = (await useSupabaseSelect("createddocuments","*, statementallocations(*), customer(id,name)"))
|
createddocuments.value = (await useEntities("createddocuments").select("*, statementallocations(*), customer(id,name)"))
|
||||||
const documents = createddocuments.value.filter(i => i.type === "invoices" ||i.type === "advanceInvoices")
|
const documents = createddocuments.value.filter(i => i.type === "invoices" ||i.type === "advanceInvoices")
|
||||||
const incominginvoices = (await useSupabaseSelect("incominginvoices","*, statementallocations(*), vendor(id,name)")).filter(i => i.state === "Gebucht")
|
const incominginvoices = (await useEntities("incominginvoices").select("*, statementallocations(*), vendor(id,name)")).filter(i => i.state === "Gebucht")
|
||||||
|
|
||||||
accounts.value = (await supabase.from("accounts").select().order("number",{ascending: true})).data
|
accounts.value = (await useEntities("accounts").selectSpecial("*","number",true))
|
||||||
ownaccounts.value = (await supabase.from("ownaccounts").select()).data
|
ownaccounts.value = (await useEntities("ownaccounts").select())
|
||||||
customers.value = (await supabase.from("customers").select()).data
|
customers.value = (await useEntities("customers").select())
|
||||||
vendors.value = (await supabase.from("vendors").select()).data
|
vendors.value = (await useEntities("vendors").select())
|
||||||
|
|
||||||
openDocuments.value = documents.filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== useSum().getCreatedDocumentSum(i,createddocuments.value).toFixed(2))
|
openDocuments.value = documents.filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== useSum().getCreatedDocumentSum(i,createddocuments.value).toFixed(2))
|
||||||
openDocuments.value = openDocuments.value.map(i => {
|
openDocuments.value = openDocuments.value.map(i => {
|
||||||
@@ -71,15 +69,12 @@ const setup = async () => {
|
|||||||
allocatedIncomingInvoices.value = incominginvoices.filter(i => i.statementallocations.find(x => x.bs_id === itemInfo.value.id))
|
allocatedIncomingInvoices.value = incominginvoices.filter(i => i.statementallocations.find(x => x.bs_id === itemInfo.value.id))
|
||||||
console.log(allocatedDocuments.value)
|
console.log(allocatedDocuments.value)
|
||||||
console.log(allocatedIncomingInvoices.value)
|
console.log(allocatedIncomingInvoices.value)
|
||||||
//openIncomingInvoices.value = (await useSupabaseSelect("incominginvoices","*, statementallocations(*), vendor(*)")).filter(i => i.statementallocations.length === 0 )
|
openIncomingInvoices.value = (await useEntities("incominginvoices").select("*, statementallocations(*), vendor(*)")).filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== getInvoiceSum(i,false))
|
||||||
openIncomingInvoices.value = (await useSupabaseSelect("incominginvoices","*, statementallocations(*), vendor(*)")).filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== getInvoiceSum(i,false))
|
|
||||||
//console.log(openIncomingInvoices.value)
|
//console.log(openIncomingInvoices.value)
|
||||||
|
|
||||||
// return incominginvoices.value.filter(i => bankstatements.value.filter(x => x.assignments.find(y => y.type === 'incomingInvoice' && y.id === i.id)).length === 0)
|
// return incominginvoices.value.filter(i => bankstatements.value.filter(x => x.assignments.find(y => y.type === 'incomingInvoice' && y.id === i.id)).length === 0)
|
||||||
|
|
||||||
|
|
||||||
let allocations = (await supabase.from("createddocuments").select(`*, statementallocations(*)`).eq("id",50)).data
|
|
||||||
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -151,15 +146,17 @@ const showMoreWithoutRecipe = ref(false)
|
|||||||
const showMoreText = ref(false)
|
const showMoreText = ref(false)
|
||||||
|
|
||||||
const saveAllocation = async (allocation) => {
|
const saveAllocation = async (allocation) => {
|
||||||
|
//TODO: BACKEND CHANGE SAVE/REMOVE
|
||||||
console.log(allocation)
|
console.log(allocation)
|
||||||
|
|
||||||
const {data,error} = await supabase.from("statementallocations").insert({
|
const res = await useNuxtApp().$api("/api/banking/statements",{
|
||||||
...allocation,
|
method: "POST",
|
||||||
tenant: profileStore.currentTenant
|
body: {
|
||||||
}).select()
|
data: allocation
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if(data) {
|
if(res) {
|
||||||
await setup()
|
await setup()
|
||||||
accountToSave.value = null
|
accountToSave.value = null
|
||||||
vendorAccountToSave.value = null
|
vendorAccountToSave.value = null
|
||||||
@@ -173,7 +170,9 @@ const saveAllocation = async (allocation) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const removeAllocation = async (allocationId) => {
|
const removeAllocation = async (allocationId) => {
|
||||||
const {data,error} = await supabase.from("statementallocations").delete().eq("id",allocationId)
|
const res = await useNuxtApp().$api(`/api/banking/statements/${allocationId}`,{
|
||||||
|
method: "DELETE"
|
||||||
|
})
|
||||||
|
|
||||||
await setup()
|
await setup()
|
||||||
}
|
}
|
||||||
@@ -202,15 +201,7 @@ const archiveStatement = async () => {
|
|||||||
let temp = itemInfo.value
|
let temp = itemInfo.value
|
||||||
delete temp.statementallocations
|
delete temp.statementallocations
|
||||||
|
|
||||||
await dataStore.updateItem("bankstatements", {...temp,archived:true})
|
await useEntities("bankstatements").archive(temp.id)
|
||||||
|
|
||||||
|
|
||||||
const {data,error} = await supabase.from("historyitems").insert({
|
|
||||||
createdBy: useProfileStore().activeProfile.id,
|
|
||||||
tenant: useProfileStore().currentTenant,
|
|
||||||
text: "Bankbuchung archiviert",
|
|
||||||
bankStatement: itemInfo.value.id
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -254,19 +245,12 @@ const archiveStatement = async () => {
|
|||||||
</UBadge>
|
</UBadge>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<ButtonWithConfirm
|
<ArchiveButton
|
||||||
color="rose"
|
color="rose"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
type="bankstatements"
|
||||||
@confirmed="archiveStatement"
|
@confirmed="archiveStatement"
|
||||||
>
|
/>
|
||||||
<template #button>
|
|
||||||
Archivieren
|
|
||||||
</template>
|
|
||||||
<template #header>
|
|
||||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
|
||||||
</template>
|
|
||||||
Möchten Sie die Kontobewegung wirklich archivieren?
|
|
||||||
</ButtonWithConfirm>
|
|
||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
|
|
||||||
@@ -356,7 +340,9 @@ const archiveStatement = async () => {
|
|||||||
<span class="font-semibold">Konto:</span>
|
<span class="font-semibold">Konto:</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<!--
|
||||||
{{dataStore.getBankAccountById(itemInfo.account) ? dataStore.getBankAccountById(itemInfo.account).name || separateIBAN(dataStore.getBankAccountById(itemInfo.account).iban) : ""}}
|
{{dataStore.getBankAccountById(itemInfo.account) ? dataStore.getBankAccountById(itemInfo.account).name || separateIBAN(dataStore.getBankAccountById(itemInfo.account).iban) : ""}}
|
||||||
|
-->
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- <tr class="flex-row flex justify-between">
|
<!-- <tr class="flex-row flex justify-between">
|
||||||
|
|||||||
@@ -7,15 +7,12 @@ import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
|
|||||||
import interactionPlugin from "@fullcalendar/interaction";
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
definePageMeta({
|
//TODO BACKEND CHANGE COLOR IN TENANT FOR RENDERING
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
//Config
|
//Config
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const mode = ref(route.params.mode || "grid")
|
const mode = ref(route.params.mode || "grid")
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
@@ -130,13 +127,13 @@ const calendarOptionsTimeline = ref({
|
|||||||
|
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
let tempData = (await useSupabaseSelect("events", "*")).filter(i => !i.archived)
|
let tempData = (await useEntities("events").select()).filter(i => !i.archived)
|
||||||
let absencerequests = (await useSupabaseSelect("absencerequests", "*, profile(*)")).filter(i => !i.archived)
|
let absencerequests = (await useEntities("absencerequests").select("*, profile(*)")).filter(i => !i.archived)
|
||||||
let projects = (await useSupabaseSelect("projects", "*")).filter(i => !i.archived)
|
let projects = (await useEntities("projects").select( "*")).filter(i => !i.archived)
|
||||||
let inventoryitems = (await useSupabaseSelect("inventoryitems", "*")).filter(i => !i.archived)
|
let inventoryitems = (await useEntities("inventoryitems").select()).filter(i => !i.archived)
|
||||||
let inventoryitemgroups = (await useSupabaseSelect("inventoryitemgroups", "*")).filter(i => !i.archived)
|
let inventoryitemgroups = (await useEntities("inventoryitemgroups").select()).filter(i => !i.archived)
|
||||||
let profiles = (await useSupabaseSelect("profiles", "*")).filter(i => !i.archived)
|
let profiles = (await useEntities("profiles").select()).filter(i => !i.archived)
|
||||||
let vehicles = (await useSupabaseSelect("vehicles", "*")).filter(i => !i.archived)
|
let vehicles = (await useEntities("vehicles").select()).filter(i => !i.archived)
|
||||||
|
|
||||||
calendarOptionsGrid.value.initialEvents = [
|
calendarOptionsGrid.value.initialEvents = [
|
||||||
...tempData.map(event => {
|
...tempData.map(event => {
|
||||||
|
|||||||
108
pages/chat.vue
108
pages/chat.vue
@@ -1,108 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
layout: "default"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const user = useSupabaseUser()
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const selectedChat = ref({})
|
|
||||||
const messageText = ref("")
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex h-full">
|
|
||||||
<div class="w-1/5 mr-2 scrollList">
|
|
||||||
<a
|
|
||||||
v-for="chat in dataStore.chats"
|
|
||||||
@click="selectedChat = chat"
|
|
||||||
>
|
|
||||||
<UAlert
|
|
||||||
: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"
|
|
||||||
>
|
|
||||||
<template #title="{title}">
|
|
||||||
{{title}} <!-- TODO: Add Unread Counter <UBadge class="ml-1">{{dataStore.getMessagesByChatId(chat.id).filter(i => !i.read ).length}}</UBadge>-->
|
|
||||||
</template>
|
|
||||||
</UAlert>
|
|
||||||
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="w-full h-full px-5 flex flex-col justify-between" v-if="selectedChat.id">
|
|
||||||
<div class="flex flex-col mt-5 scrollList">
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-for="message in dataStore.getMessagesByChatId(selectedChat.id)"
|
|
||||||
>
|
|
||||||
<div class="flex justify-end mb-4" v-if="message.origin === user.id">
|
|
||||||
<div
|
|
||||||
class="mr-2 py-3 px-4 bg-primary-400 rounded-bl-3xl rounded-tl-3xl rounded-tr-xl text-white"
|
|
||||||
>
|
|
||||||
{{message.text}}
|
|
||||||
</div>
|
|
||||||
<UAvatar
|
|
||||||
:alt="profileStore.getProfileById(message.origin) ? profileStore.getProfileById(message.origin).fullName : ''"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-start mb-4" v-else>
|
|
||||||
<UAvatar
|
|
||||||
:alt="profileStore.getProfileById(message.origin) ? profileStore.getProfileById(message.origin).fullName : ''"
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="ml-2 py-3 px-4 bg-gray-400 rounded-br-3xl rounded-tr-3xl rounded-tl-xl text-white"
|
|
||||||
>
|
|
||||||
{{message.text}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="py-5">
|
|
||||||
<UButtonGroup class="w-full">
|
|
||||||
<UInput
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
placeholder="Neue Nachricht"
|
|
||||||
v-model="messageText"
|
|
||||||
@keyup.enter="dataStore.createNewItem('messages',{
|
|
||||||
text: messageText,
|
|
||||||
origin: user.id,
|
|
||||||
destination: selectedChat.id
|
|
||||||
})"
|
|
||||||
class="flex-auto"
|
|
||||||
/>
|
|
||||||
<UButton
|
|
||||||
@click="dataStore.createNewItem('messages',{
|
|
||||||
text: messageText,
|
|
||||||
origin: user.id,
|
|
||||||
destination: selectedChat.id
|
|
||||||
})"
|
|
||||||
>
|
|
||||||
Senden
|
|
||||||
</UButton>
|
|
||||||
</UButtonGroup>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const profiles = ref([])
|
|
||||||
|
|
||||||
const itemInfo = ref({})
|
|
||||||
const selectedProfiles = ref([])
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
profiles.value = await useSupabaseSelect("profiles")
|
|
||||||
selectedProfiles.value = [profileStore.activeProfile.id]
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
|
|
||||||
const createChat = async () => {
|
|
||||||
const {data,error} = await supabase.from("chats").insert({
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
name: itemInfo.value.name
|
|
||||||
}).select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else if(data){
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
let memberships = selectedProfiles.value.map(i => {
|
|
||||||
return {
|
|
||||||
profile_id: i,
|
|
||||||
chat_id: data[0].id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const {error} = await supabase.from("chatmembers").insert(memberships)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
navigateTo("/chats")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar>
|
|
||||||
<template #left>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-left"
|
|
||||||
variant="outline"
|
|
||||||
@click="navigateTo(`/chats`)"
|
|
||||||
>
|
|
||||||
Chats
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
<template #center>
|
|
||||||
<h1
|
|
||||||
v-if="itemInfo"
|
|
||||||
class="text-xl font-medium"
|
|
||||||
>Chat Erstellen</h1>
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
@click="createChat"
|
|
||||||
>
|
|
||||||
Erstellen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="navigateTo(`/chats`)"
|
|
||||||
color="red"
|
|
||||||
class="ml-2"
|
|
||||||
>
|
|
||||||
Abbrechen
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardPanelContent>
|
|
||||||
<UForm>
|
|
||||||
<UFormGroup
|
|
||||||
label="Name:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.name"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Beteiligte Benutzer:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="selectedProfiles"
|
|
||||||
:options="profiles"
|
|
||||||
option-attribute="fullName"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
multiple
|
|
||||||
:search-attributes="['fullName']"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{selectedProfiles.length > 0 ? selectedProfiles.map(i => profileStore.getProfileById(i).fullName).join(", ") : "Keine Benutzer ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
</UForm>
|
|
||||||
</UDashboardPanelContent>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'/': () => {
|
|
||||||
//console.log(searchinput)
|
|
||||||
//searchinput.value.focus()
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},
|
|
||||||
'+': () => {
|
|
||||||
router.push("/chats/create")
|
|
||||||
},
|
|
||||||
'Enter': {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
router.push(`/chats/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 router = useRouter()
|
|
||||||
const items = ref([])
|
|
||||||
const selectedItem = ref(0)
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
items.value = await useSupabaseSelect("chats","*, profiles(*)")
|
|
||||||
//items.value = await useSupabaseSelect("chats","*, profiles(*), chatmessages(*)")
|
|
||||||
}
|
|
||||||
|
|
||||||
const templateColumns = [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "Name"
|
|
||||||
},{
|
|
||||||
key: "profiles",
|
|
||||||
label: "Mitglieder"
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
setup()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar title="Chats" :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(`/chats/create`)">+ Chat</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(`/chats/show/${i.id}`) "
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Chats anzuzeigen' }"
|
|
||||||
>
|
|
||||||
<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 #profiles-data="{row}">
|
|
||||||
{{row.profiles.map(i => i.fullName).join(", ")}}
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { format, isToday } from 'date-fns'
|
|
||||||
import dayjs from "dayjs"
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
' ': () => {
|
|
||||||
document.getElementById("textinput").focus()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const itemInfo = ref({})
|
|
||||||
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(profileStore.activeProfile.id))
|
|
||||||
|
|
||||||
|
|
||||||
for await (const message of unseenMessages){
|
|
||||||
await supabase.from("chatmessages").update({seenBy: [...message.seenBy, profileStore.activeProfile.id]}).eq("id",message.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
setup()
|
|
||||||
|
|
||||||
const messageText = ref("")
|
|
||||||
const sendMessage = async () => {
|
|
||||||
if(messageText.value.length > 0) {
|
|
||||||
const message = {
|
|
||||||
origin: profileStore.activeProfile.id,
|
|
||||||
destinationchat: itemInfo.value.id,
|
|
||||||
text: messageText.value,
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
seenBy: [profileStore.activeProfile.id]
|
|
||||||
}
|
|
||||||
|
|
||||||
const {data,error} = await supabase.from("chatmessages").insert(message)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
//Reset
|
|
||||||
messageText.value = ""
|
|
||||||
//Create Notifications
|
|
||||||
let notifications = itemInfo.value.profiles.filter(i => i.id !== profileStore.activeProfile.id).map(i => {
|
|
||||||
return {
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
profile: i.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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(notifications)
|
|
||||||
|
|
||||||
const {error} = await supabase.from("notifications").insert(notifications)
|
|
||||||
|
|
||||||
setup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar :title="itemInfo.name">
|
|
||||||
<template #center>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<UAvatarGroup size="sm" :max="2">
|
|
||||||
<UAvatar
|
|
||||||
v-if="itemInfo.profiles"
|
|
||||||
v-for="avatar in itemInfo.profiles.map(i => i.fullName)"
|
|
||||||
:alt="avatar" size="sm" />
|
|
||||||
</UAvatarGroup>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<div class="scrollList p-5">
|
|
||||||
<UAlert
|
|
||||||
: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: 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>
|
|
||||||
<div class="flex flex-row justify-between p-5">
|
|
||||||
<UInput
|
|
||||||
class="flex-auto mr-2"
|
|
||||||
v-model="messageText"
|
|
||||||
@keyup.enter="sendMessage"
|
|
||||||
placeholder="Deine Nachricht"
|
|
||||||
id="textinput"
|
|
||||||
/>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-double-right-solid"
|
|
||||||
@click="sendMessage"
|
|
||||||
:disabled="messageText.length === 0"
|
|
||||||
>
|
|
||||||
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import dayjs from "dayjs"
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
|
|
||||||
const items = ref([])
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
|
|
||||||
items.value = (await supabase.from("historyitems").select().like('text',`%@${profileStore.activeProfile.username}%`)/*.textSearch("text", `'@${profileStore.activeProfile.username}'`)*/.order("created_at")).data
|
|
||||||
}
|
|
||||||
|
|
||||||
const navigateToHistoryItem = (item) => {
|
|
||||||
/*if(item.customer) {
|
|
||||||
router.push(`/customers/show/${item.customer}`)
|
|
||||||
} else if(item.vendor) {
|
|
||||||
router.push(`/vendors/show/${item.vendor}`)
|
|
||||||
} else if(item.project) {
|
|
||||||
router.push(`/projects/show/${item.project}`)
|
|
||||||
} else if(item.plant) {
|
|
||||||
router.push(`/plants/show/${item.plant}`)
|
|
||||||
} else if(item.incomingInvoice) {
|
|
||||||
router.push(`/incomingInvoices/show/${item.incomingInvoice}`)
|
|
||||||
}/!* else if(item.document) {
|
|
||||||
router.push(`/documents/show/${item.document}`)
|
|
||||||
}*!/ else if(item.contact) {
|
|
||||||
router.push(`/contacts/show/${item.contact}`)
|
|
||||||
} else if(item.inventoryitem) {
|
|
||||||
router.push(`/inventoryitems/show/${item.inventoryitem}`)
|
|
||||||
} else if(item.product) {
|
|
||||||
router.push(`/products/show/${item.product}`)
|
|
||||||
} else if(item.profile) {
|
|
||||||
router.push(`/profiles/show/${item.profile}`)
|
|
||||||
} else if(item.absenceRequest) {
|
|
||||||
router.push(`/absenceRequests/show/${item.absenceRequest}`)
|
|
||||||
} else if(item.event) {
|
|
||||||
router.push(`/events/show/${item.event}`)
|
|
||||||
} else if(item.task) {
|
|
||||||
router.push(`/tasks/show/${item.task}`)
|
|
||||||
} else if(item.vehicle) {
|
|
||||||
router.push(`/vehicle/show/${item.vehicle}`)
|
|
||||||
} else if(item.bankStatement) {
|
|
||||||
router.push(`/bankStatements/show/${item.bankStatement}`)
|
|
||||||
} else if(item.space) {
|
|
||||||
router.push(`/spaces/show/${item.space}`)
|
|
||||||
} else if(item.trackingtrip) {
|
|
||||||
router.push(`/trackingtrips/show/${item.trackingtrip}`)
|
|
||||||
}*/
|
|
||||||
if(item.config && item.config.type !== "document") {
|
|
||||||
router.push(`/${item.config.type}s/show/${item.config.id}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar title="Erwähnungen in Logbüchern" :badge="items.length">
|
|
||||||
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UTable
|
|
||||||
:rows="items"
|
|
||||||
@select="navigateToHistoryItem"
|
|
||||||
:columns="[{key:'created_at',label:'Datum'},{key:'config',label:'Typ'},{key:'text',label:'Text'}]"
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Erwähnungen anzuzeigen' }"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
|
||||||
>
|
|
||||||
<template #config-data="{row}">
|
|
||||||
<span v-if="row.config">{{dataStore.dataTypes[row.config.type + "s"].labelSingle}}</span>
|
|
||||||
</template>
|
|
||||||
<template #created_at-data="{row}">
|
|
||||||
{{dayjs(row.created_at).format("HH:mm DD.MM.YYYY")}}
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,340 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'backspace': () => {
|
|
||||||
router.push("/contacts")
|
|
||||||
},
|
|
||||||
'arrowleft': () => {
|
|
||||||
if(openTab.value > 0){
|
|
||||||
openTab.value -= 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowright': () => {
|
|
||||||
if(openTab.value < 3) {
|
|
||||||
openTab.value += 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
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({
|
|
||||||
active: true,
|
|
||||||
profiles: [profileStore.activeProfile.id]
|
|
||||||
})
|
|
||||||
const oldItemInfo = ref({})
|
|
||||||
|
|
||||||
//Functions
|
|
||||||
const setupPage = async () => {
|
|
||||||
if(mode.value === "show"){
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("contacts",route.params.id,"*, customer(id, name), vendor(id,name) ")
|
|
||||||
} else if(mode.value === "edit") {
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("contacts",route.params.id,"*")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mode.value === "create") {
|
|
||||||
let query = route.query
|
|
||||||
|
|
||||||
if(query.customer) itemInfo.value.customer = Number(query.customer)
|
|
||||||
if(query.vendor) itemInfo.value.vendor = Number(query.vendor)
|
|
||||||
}
|
|
||||||
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
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(`/contacts`)"
|
|
||||||
>
|
|
||||||
Ansprechpartner
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
<template #center>
|
|
||||||
<h1
|
|
||||||
v-if="itemInfo"
|
|
||||||
:class="['text-xl','font-medium', ... itemInfo.active ? ['text-primary'] : ['text-rose-500']]"
|
|
||||||
>{{itemInfo.id ? `Ansprechpartner: ${itemInfo.fullName}` : (mode === 'create' ? 'Ansprechpartner erstellen' : 'Ansprechpartner bearbeiten')}}</h1>
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
v-if="mode === 'edit'"
|
|
||||||
@click="dataStore.updateItem('contacts',{...itemInfo, fullName: itemInfo.firstName + ' ' + itemInfo.lastName},oldItemInfo)"
|
|
||||||
>
|
|
||||||
Speichern
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
v-else-if="mode === 'create'"
|
|
||||||
@click="dataStore.createNewItem('contacts',{...itemInfo, fullName: itemInfo.firstName + ' ' + itemInfo.lastName})"
|
|
||||||
>
|
|
||||||
Erstellen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="router.push(itemInfo.id ? `/contacts/show/${itemInfo.value.id}` : `/contacts/`)"
|
|
||||||
color="red"
|
|
||||||
class="ml-2"
|
|
||||||
v-if="mode === 'edit' || mode === 'create'"
|
|
||||||
>
|
|
||||||
Abbrechen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
v-if="mode === 'show'"
|
|
||||||
@click="router.push(`/contacts/edit/${itemInfo.id}`)"
|
|
||||||
>
|
|
||||||
Bearbeiten
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
<!-- <template #badge v-if="itemInfo">
|
|
||||||
<UBadge
|
|
||||||
v-if="itemInfo.active"
|
|
||||||
>
|
|
||||||
Kontakt aktiv
|
|
||||||
</UBadge>
|
|
||||||
<UBadge
|
|
||||||
v-else
|
|
||||||
color="red"
|
|
||||||
>
|
|
||||||
Kontakt inaktiv
|
|
||||||
</UBadge>
|
|
||||||
</template>-->
|
|
||||||
</UDashboardNavbar>
|
|
||||||
|
|
||||||
<UTabs
|
|
||||||
:items="[{label: 'Informationen'}, {label: 'Logbuch'}]"
|
|
||||||
v-if="itemInfo && mode == 'show'"
|
|
||||||
class="p-5"
|
|
||||||
v-model="openTab"
|
|
||||||
>
|
|
||||||
<template #item="{item}">
|
|
||||||
<div v-if="item.label === 'Informationen'" class="flex flex-row mt-5">
|
|
||||||
<div class="w-1/2 mr-5">
|
|
||||||
<UCard>
|
|
||||||
<Toolbar>
|
|
||||||
<UButton
|
|
||||||
@click="router.push(`/email/new?to=${itemInfo.email}`)"
|
|
||||||
icon="i-heroicons-envelope"
|
|
||||||
:disabled="!itemInfo.email"
|
|
||||||
>
|
|
||||||
E-Mail
|
|
||||||
</UButton>
|
|
||||||
</Toolbar>
|
|
||||||
<table class="w-full">
|
|
||||||
<tr>
|
|
||||||
<td>Kunde: </td>
|
|
||||||
<td><nuxt-link v-if="itemInfo.customer" :to="`/customers/show/${itemInfo.customer?.id}`">{{itemInfo?.customer?.name}}</nuxt-link></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Lieferant: </td>
|
|
||||||
<td><nuxt-link v-if="itemInfo.vendor" :to="`/customers/show/${itemInfo.vendor?.id}`">{{itemInfo?.vendor?.name}}</nuxt-link></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>E-Mail:</td>
|
|
||||||
<td>{{itemInfo.email}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Mobil:</td>
|
|
||||||
<td>{{itemInfo.phoneMobile}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Festnetz:</td>
|
|
||||||
<td>{{itemInfo.phoneHome}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Rolle:</td>
|
|
||||||
<td>{{itemInfo.role}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Geburtstag:</td>
|
|
||||||
<td>{{itemInfo.birthday ? dayjs(itemInfo.birthday).format("DD.MM.YYYY") : ""}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Notizen:</td>
|
|
||||||
<td>{{itemInfo.notes}}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</UCard>
|
|
||||||
</div>
|
|
||||||
<div class="w-1/2">
|
|
||||||
<UCard>
|
|
||||||
<HistoryDisplay
|
|
||||||
type="contact"
|
|
||||||
v-if="itemInfo"
|
|
||||||
:element-id="itemInfo.id"
|
|
||||||
/>
|
|
||||||
</UCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</UTabs>
|
|
||||||
<UForm
|
|
||||||
v-else-if="mode == 'edit' || mode == 'create'"
|
|
||||||
class="p-5"
|
|
||||||
>
|
|
||||||
<UFormGroup
|
|
||||||
label="Anrede:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.salutation"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Vorname:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.firstName"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Nachname:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.lastName"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Kunde:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="itemInfo.customer"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
:options="dataStore.customers"
|
|
||||||
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="Lieferant:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="itemInfo.vendor"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
:options="dataStore.vendors"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['name']"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor) ? dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor).name : "Lieferant auswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Kontakt aktiv:"
|
|
||||||
>
|
|
||||||
<UCheckbox
|
|
||||||
v-model="itemInfo.active"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="E-Mail:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.email"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Mobil:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.phoneMobile"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Festnetz:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.phoneHome"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Rolle:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.role"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Geburtstag:"
|
|
||||||
>
|
|
||||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
||||||
<UButton
|
|
||||||
variant="outline"
|
|
||||||
icon="i-heroicons-calendar-days-20-solid"
|
|
||||||
:label="itemInfo.birthday ? dayjs(itemInfo.birthday).format('DD.MM.YYYY') : 'Datum auswählen'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #panel="{ close }">
|
|
||||||
<LazyDatePicker
|
|
||||||
v-model="itemInfo.birthday"
|
|
||||||
mode="date"
|
|
||||||
/>
|
|
||||||
</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:"
|
|
||||||
>
|
|
||||||
<UTextarea
|
|
||||||
v-model="itemInfo.notes"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
</UForm>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
td {
|
|
||||||
border-bottom: 1px solid lightgrey;
|
|
||||||
vertical-align: top;
|
|
||||||
padding-bottom: 0.15em;
|
|
||||||
padding-top: 0.15em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<EntityList
|
|
||||||
type="contacts"
|
|
||||||
:items="items"
|
|
||||||
></EntityList>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import EntityList from "~/components/EntityList.vue";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
const items = ref([])
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
items.value = await useSupabaseSelect("contacts","*, customer(name), vendor(name)")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -9,13 +9,11 @@ const dataStore = useDataStore()
|
|||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const itemInfo = ref({
|
const itemInfo = ref({
|
||||||
@@ -37,17 +35,15 @@ const itemInfo = ref({
|
|||||||
deliveryDateEnd: null,
|
deliveryDateEnd: null,
|
||||||
deliveryDateType: "Lieferdatum",
|
deliveryDateType: "Lieferdatum",
|
||||||
dateOfPerformance: null,
|
dateOfPerformance: null,
|
||||||
paymentDays: profileStore.ownTenant.standardPaymentDays,
|
paymentDays: auth.activeTenantData.standardPaymentDays,
|
||||||
customSurchargePercentage: 0,
|
customSurchargePercentage: 0,
|
||||||
createdBy: profileStore.activeProfile.id,
|
createdBy: auth.user.id,
|
||||||
title: null,
|
title: null,
|
||||||
description: null,
|
description: null,
|
||||||
startText: null,
|
startText: null,
|
||||||
endText: null,
|
endText: null,
|
||||||
rows: [
|
rows: [],
|
||||||
|
contactPerson: auth.user.id,
|
||||||
],
|
|
||||||
contactPerson: profileStore.activeProfile.id,
|
|
||||||
contactPersonName: null,
|
contactPersonName: null,
|
||||||
contactTel: null,
|
contactTel: null,
|
||||||
contactEMail: null,
|
contactEMail: null,
|
||||||
@@ -63,7 +59,7 @@ const itemInfo = ref({
|
|||||||
usedAdvanceInvoices: []
|
usedAdvanceInvoices: []
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(profileStore.ownTenant)
|
console.log(auth.activeTenantData)
|
||||||
|
|
||||||
const letterheads = ref([])
|
const letterheads = ref([])
|
||||||
const createddocuments = ref([])
|
const createddocuments = ref([])
|
||||||
@@ -78,33 +74,42 @@ const selectedServicecategorie = ref(null)
|
|||||||
const customers = ref([])
|
const customers = ref([])
|
||||||
const contacts = ref([])
|
const contacts = ref([])
|
||||||
const texttemplates = ref([])
|
const texttemplates = ref([])
|
||||||
|
const units = ref([])
|
||||||
|
const tenantUsers = ref([])
|
||||||
|
|
||||||
|
const setupData = async () => {
|
||||||
|
letterheads.value = (await useEntities("letterheads").select("*")).filter(i => i.documentTypes.length === 0 || i.documentTypes.includes(itemInfo.value.type))
|
||||||
|
createddocuments.value = await useEntities("createddocuments").select("*")
|
||||||
|
projects.value = await useEntities("projects").select("*")
|
||||||
|
plants.value = await useEntities("plants").select("*")
|
||||||
|
services.value = await useEntities("services").select("*")
|
||||||
|
servicecategories.value = await useEntities("servicecategories").select("*")
|
||||||
|
products.value = await useEntities("products").select("*")
|
||||||
|
productcategories.value = await useEntities("productcategories").select("*")
|
||||||
|
customers.value = await useEntities("customers").select("*", "customerNumber")
|
||||||
|
contacts.value = await useEntities("contacts").select("*")
|
||||||
|
texttemplates.value = await useEntities("texttemplates").select("*")
|
||||||
|
units.value = await useEntities("units").selectSpecial("*")
|
||||||
|
tenantUsers.value = (await useNuxtApp().$api(`/api/tenant/users`, {
|
||||||
|
method: "GET"
|
||||||
|
})).users
|
||||||
|
}
|
||||||
|
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
|
|
||||||
|
await setupData()
|
||||||
|
|
||||||
letterheads.value = (await useSupabaseSelect("letterheads","*")).filter(i => i.documentTypes.length === 0 || i.documentTypes.includes(itemInfo.value.type))
|
|
||||||
createddocuments.value = (await useSupabaseSelect("createddocuments","*"))
|
|
||||||
projects.value = (await useSupabaseSelect("projects","*"))
|
|
||||||
plants.value = (await useSupabaseSelect("plants","*"))
|
|
||||||
services.value = (await useSupabaseSelect("services","*"))
|
|
||||||
servicecategories.value = (await useSupabaseSelect("servicecategories","*"))
|
|
||||||
products.value = (await useSupabaseSelect("products","*"))
|
|
||||||
productcategories.value = (await useSupabaseSelect("productcategories","*"))
|
|
||||||
customers.value = (await useSupabaseSelect("customers","*","customerNumber"))
|
|
||||||
contacts.value = (await useSupabaseSelect("contacts","*"))
|
|
||||||
texttemplates.value = (await useSupabaseSelect("texttemplates","*"))
|
|
||||||
if (productcategories.value.length > 0) selectedProductcategorie.value = productcategories.value[0].id
|
if (productcategories.value.length > 0) selectedProductcategorie.value = productcategories.value[0].id
|
||||||
if (servicecategories.value.length > 0) selectedServicecategorie.value = servicecategories.value[0].id
|
if (servicecategories.value.length > 0) selectedServicecategorie.value = servicecategories.value[0].id
|
||||||
|
|
||||||
if (route.params) {
|
if (route.params) {
|
||||||
if (route.params.id) {
|
if (route.params.id) {
|
||||||
itemInfo.value = await useSupabaseSelectSingle("createddocuments", route.params.id)
|
console.log(route.params)
|
||||||
|
itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id)
|
||||||
checkCompatibilityWithInputPrice()
|
checkCompatibilityWithInputPrice()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(itemInfo.value.project) await checkForOpenAdvanceInvoices()
|
|
||||||
|
|
||||||
if (!itemInfo.value.deliveryDateType) itemInfo.value.deliveryDateType = "Lieferdatum"
|
if (!itemInfo.value.deliveryDateType) itemInfo.value.deliveryDateType = "Lieferdatum"
|
||||||
|
|
||||||
@@ -124,7 +129,6 @@ const setupPage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setContactPersonData()
|
setContactPersonData()
|
||||||
|
|
||||||
if (route.query.linkedDocuments) {
|
if (route.query.linkedDocuments) {
|
||||||
@@ -132,7 +136,7 @@ const setupPage = async () => {
|
|||||||
console.log(route.query.loadMode)
|
console.log(route.query.loadMode)
|
||||||
|
|
||||||
if (route.query.loadMode === "deliveryNotes") {
|
if (route.query.loadMode === "deliveryNotes") {
|
||||||
let linkedDocuments = (await supabase.from("createddocuments").select().in("id",JSON.parse(route.query.linkedDocuments))).data
|
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||||
|
|
||||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||||
|
|
||||||
@@ -200,7 +204,7 @@ const setupPage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (route.query.loadMode === "finalInvoice") {
|
} else if (route.query.loadMode === "finalInvoice") {
|
||||||
let linkedDocuments = (await supabase.from("createddocuments").select().in("id",JSON.parse(route.query.linkedDocuments))).data
|
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||||
|
|
||||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||||
|
|
||||||
@@ -213,7 +217,7 @@ const setupPage = async () => {
|
|||||||
setCustomerData()
|
setCustomerData()
|
||||||
|
|
||||||
for await (const doc of linkedDocuments.filter(i => i.type === "confirmationOrders")) {
|
for await (const doc of linkedDocuments.filter(i => i.type === "confirmationOrders")) {
|
||||||
let linkedDocument = await useSupabaseSelectSingle("createddocuments",doc.id)
|
let linkedDocument = await useEntities("createddocuments").selectSingle(doc.id)
|
||||||
|
|
||||||
itemInfo.value.rows.push({
|
itemInfo.value.rows.push({
|
||||||
mode: "title",
|
mode: "title",
|
||||||
@@ -224,7 +228,7 @@ const setupPage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for await (const doc of linkedDocuments.filter(i => i.type === "quotes")) {
|
for await (const doc of linkedDocuments.filter(i => i.type === "quotes")) {
|
||||||
let linkedDocument = await useSupabaseSelectSingle("createddocuments",doc.id)
|
let linkedDocument = await useEntities("createddocuments").selectSingle(doc.id)
|
||||||
|
|
||||||
itemInfo.value.rows.push({
|
itemInfo.value.rows.push({
|
||||||
mode: "title",
|
mode: "title",
|
||||||
@@ -266,19 +270,14 @@ const setupPage = async () => {
|
|||||||
setPosNumbers()
|
setPosNumbers()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.query.linkedDocument) {
|
if (route.query.linkedDocument) {
|
||||||
itemInfo.value.linkedDocument = route.query.linkedDocument
|
itemInfo.value.linkedDocument = route.query.linkedDocument
|
||||||
let linkedDocument = await useSupabaseSelectSingle("createddocuments",itemInfo.value.linkedDocument)
|
let linkedDocument = await useEntities("createddocuments").selectSingle(itemInfo.value.linkedDocument)
|
||||||
|
|
||||||
|
|
||||||
if (route.query.optionsToImport) {
|
if (route.query.optionsToImport) {
|
||||||
//Import only true
|
//Import only true
|
||||||
@@ -333,7 +332,6 @@ const setupPage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
checkCompatibilityWithInputPrice()
|
checkCompatibilityWithInputPrice()
|
||||||
|
|
||||||
if (route.query.loadMode === "storno") {
|
if (route.query.loadMode === "storno") {
|
||||||
@@ -345,7 +343,6 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
itemInfo.value.usedAdvanceInvoices = linkedDocument.usedAdvanceInvoices
|
itemInfo.value.usedAdvanceInvoices = linkedDocument.usedAdvanceInvoices
|
||||||
|
|
||||||
checkForOpenAdvanceInvoices()
|
|
||||||
|
|
||||||
itemInfo.value.description = `Stornorechnung zu Rechnung ${linkedDocument.documentNumber} vom ${dayjs(linkedDocument.documentDate).format('DD.MM.YYYY')}`
|
itemInfo.value.description = `Stornorechnung zu Rechnung ${linkedDocument.documentNumber} vom ${dayjs(linkedDocument.documentDate).format('DD.MM.YYYY')}`
|
||||||
|
|
||||||
@@ -361,13 +358,12 @@ const setupPage = async () => {
|
|||||||
if (route.query.project) {
|
if (route.query.project) {
|
||||||
itemInfo.value.project = Number(route.query.project)
|
itemInfo.value.project = Number(route.query.project)
|
||||||
|
|
||||||
let project = await useSupabaseSelectSingle("projects",itemInfo.value.project)
|
let project = await useEntities("projects").selectSingle(itemInfo.value.project)
|
||||||
|
|
||||||
if (!itemInfo.value.description) {
|
if (!itemInfo.value.description) {
|
||||||
itemInfo.value.description = project.customerRef
|
itemInfo.value.description = project.customerRef
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForOpenAdvanceInvoices()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,28 +378,12 @@ const setupPage = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//if(itemInfo.value.project) checkForOpenAdvanceInvoices()
|
|
||||||
|
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
const openAdvanceInvoices = ref([])
|
|
||||||
const checkForOpenAdvanceInvoices = async () => {
|
|
||||||
console.log("Check for Open Advance Invoices")
|
|
||||||
const {data} = await supabase.from("createddocuments").select().eq("project", itemInfo.value.project).eq("advanceInvoiceResolved", false).eq("type","advanceInvoices")
|
|
||||||
const {data: usedAdvanceInvoices} = await supabase.from("createddocuments").select().in("id", itemInfo.value.usedAdvanceInvoices)
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
openAdvanceInvoices.value = [...data, ...usedAdvanceInvoices.filter(i => !data.find(x => x.id === i.id))]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const addAdvanceInvoiceToInvoice = (advanceInvoice) => {
|
|
||||||
itemInfo.value.usedAdvanceInvoices.push(advanceInvoice)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDocumentTypeConfig = (withTexts = false) => {
|
const setDocumentTypeConfig = (withTexts = false) => {
|
||||||
if (itemInfo.value.type === "invoices" || itemInfo.value.type === "advanceInvoices" || itemInfo.value.type === "serialInvoices" || itemInfo.value.type === "cancellationInvoices") {
|
if (itemInfo.value.type === "invoices" || itemInfo.value.type === "advanceInvoices" || itemInfo.value.type === "serialInvoices" || itemInfo.value.type === "cancellationInvoices") {
|
||||||
@@ -467,7 +447,7 @@ const setCustomerData = async (customerId, loadOnlyAdress = false) => {
|
|||||||
itemInfo.value.customer = customerId
|
itemInfo.value.customer = customerId
|
||||||
}
|
}
|
||||||
|
|
||||||
customers.value = await useSupabaseSelect("customers")
|
customers.value = await useEntities("customers").select()
|
||||||
|
|
||||||
|
|
||||||
let customer = customers.value.find(i => i.id === itemInfo.value.customer)
|
let customer = customers.value.find(i => i.id === itemInfo.value.customer)
|
||||||
@@ -496,10 +476,12 @@ const setCustomerData = async (customerId, loadOnlyAdress = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setContactPersonData = async () => {
|
const setContactPersonData = async () => {
|
||||||
//console.log(itemInfo.value.contactPerson)
|
//console.log(itemInfo.value.contactPerson) //TODO: BACKEND CHANGE Set Profile
|
||||||
let profile = await useSupabaseSelectSingle("profiles",itemInfo.value.contactPerson, '*')
|
let profile = (await useNuxtApp().$api(`/api/user/${itemInfo.value.created_by}`, {
|
||||||
|
method: "GET"
|
||||||
|
})).profile
|
||||||
|
|
||||||
itemInfo.value.contactPersonName = profile.fullName
|
itemInfo.value.contactPersonName = auth.profile.full_name
|
||||||
itemInfo.value.contactTel = profile.mobileTel || profile.fixedTel || ""
|
itemInfo.value.contactTel = profile.mobileTel || profile.fixedTel || ""
|
||||||
itemInfo.value.contactEMail = profile.email
|
itemInfo.value.contactEMail = profile.email
|
||||||
|
|
||||||
@@ -574,7 +556,7 @@ const addPosition = (mode) => {
|
|||||||
linkedEntitys: []
|
linkedEntitys: []
|
||||||
}
|
}
|
||||||
|
|
||||||
itemInfo.value.rows.push({...rowData, ...profileStore.ownTenant.extraModules.includes("agriculture") ? {agriculture: {}}: {}})
|
itemInfo.value.rows.push({...rowData, ...auth.activeTenantData.extraModules.includes("agriculture") ? {agriculture: {}} : {}})
|
||||||
|
|
||||||
} else if (mode === 'normal') {
|
} else if (mode === 'normal') {
|
||||||
itemInfo.value.rows.push({
|
itemInfo.value.rows.push({
|
||||||
@@ -602,7 +584,7 @@ const addPosition = (mode) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Push Agriculture Holder only if Module is activated
|
//Push Agriculture Holder only if Module is activated
|
||||||
itemInfo.value.rows.push({...rowData, ...profileStore.ownTenant.extraModules.includes("agriculture") ? {agriculture: {}}: {}})
|
itemInfo.value.rows.push({...rowData, ...auth.activeTenantData.extraModules.includes("agriculture") ? {agriculture: {}} : {}})
|
||||||
} else if (mode === "pagebreak") {
|
} else if (mode === "pagebreak") {
|
||||||
itemInfo.value.rows.push({
|
itemInfo.value.rows.push({
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
@@ -641,14 +623,27 @@ const findDocumentErrors = computed(() => {
|
|||||||
if (itemInfo.value.customer === null) errors.push({message: "Es ist kein Kunde ausgewählt", type: "breaking"})
|
if (itemInfo.value.customer === null) errors.push({message: "Es ist kein Kunde ausgewählt", type: "breaking"})
|
||||||
if (itemInfo.value.contact === null) errors.push({message: "Es ist kein Kontakt ausgewählt", type: "info"})
|
if (itemInfo.value.contact === null) errors.push({message: "Es ist kein Kontakt ausgewählt", type: "info"})
|
||||||
if (itemInfo.value.letterhead === null) errors.push({message: "Es ist kein Briefpapier ausgewählt", type: "breaking"})
|
if (itemInfo.value.letterhead === null) errors.push({message: "Es ist kein Briefpapier ausgewählt", type: "breaking"})
|
||||||
if(itemInfo.value.address.street === null) errors.push({message: "Es ist keine Straße im Adressat angegeben", type: "breaking"})
|
if (itemInfo.value.created_by === null || !itemInfo.value.created_by) errors.push({message: "Es ist kein Ansprechpartner ausgewählt", type: "breaking"})
|
||||||
if(itemInfo.value.address.zip === null) errors.push({message: "Es ist keine Postleitzahl im Adressat angegeben", type: "breaking"})
|
if (itemInfo.value.address.street === null) errors.push({
|
||||||
if(itemInfo.value.address.city === null) errors.push({message: "Es ist keine Stadt im Adressat angegeben", type: "breaking"})
|
message: "Es ist keine Straße im Adressat angegeben",
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
if (itemInfo.value.address.zip === null) errors.push({
|
||||||
|
message: "Es ist keine Postleitzahl im Adressat angegeben",
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
if (itemInfo.value.address.city === null) errors.push({
|
||||||
|
message: "Es ist keine Stadt im Adressat angegeben",
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
|
||||||
if (itemInfo.value.project === null) errors.push({message: "Es ist kein Projekt ausgewählt", type: "info"})
|
if (itemInfo.value.project === null) errors.push({message: "Es ist kein Projekt ausgewählt", type: "info"})
|
||||||
|
|
||||||
if (['Lieferzeitraum', 'Leistungszeitraum'].includes(itemInfo.value.deliveryDateType) && itemInfo.value.type !== "serialInvoices") {
|
if (['Lieferzeitraum', 'Leistungszeitraum'].includes(itemInfo.value.deliveryDateType) && itemInfo.value.type !== "serialInvoices") {
|
||||||
if(itemInfo.value.deliveryDateEnd === null) errors.push({message: `Es ist kein Enddatum für den ${itemInfo.value.deliveryDateType} angegeben`, type: "breaking"})
|
if (itemInfo.value.deliveryDateEnd === null) errors.push({
|
||||||
|
message: `Es ist kein Enddatum für den ${itemInfo.value.deliveryDateType} angegeben`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -658,23 +653,47 @@ const findDocumentErrors = computed(() => {
|
|||||||
itemInfo.value.rows.forEach(row => {
|
itemInfo.value.rows.forEach(row => {
|
||||||
|
|
||||||
if (itemInfo.value.type !== "quotes" && row.optional) {
|
if (itemInfo.value.type !== "quotes" && row.optional) {
|
||||||
errors.push({message: `Position ${row.pos} ist als Optional markiert. Dies wird nur in Angeboten unterstützt.`, type: "breaking"})
|
errors.push({
|
||||||
|
message: `Position ${row.pos} ist als Optional markiert. Dies wird nur in Angeboten unterstützt.`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemInfo.value.type !== "quotes" && row.alternative) {
|
if (itemInfo.value.type !== "quotes" && row.alternative) {
|
||||||
errors.push({message: `Position ${row.pos} ist als Alternativ markiert. Dies wird nur in Angeboten unterstützt.`, type: "breaking"})
|
errors.push({
|
||||||
|
message: `Position ${row.pos} ist als Alternativ markiert. Dies wird nur in Angeboten unterstützt.`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if(row.mode === "normal" && !row.product) errors.push({message: `In Position ${row.pos} ist kein Artikel ausgewählt`, type: "breaking"})
|
if (row.mode === "normal" && !row.product) errors.push({
|
||||||
if(row.mode === "service" && !row.service) errors.push({message: `In Position ${row.pos} ist keine Leistung ausgewählt`, type: "breaking"})
|
message: `In Position ${row.pos} ist kein Artikel ausgewählt`,
|
||||||
if(row.mode === "title" && !row.text) errors.push({message: `In Position ${row.pos} ist kein Titel hinterlegt`, type: "breaking"})
|
type: "breaking"
|
||||||
|
})
|
||||||
|
if (row.mode === "service" && !row.service) errors.push({
|
||||||
|
message: `In Position ${row.pos} ist keine Leistung ausgewählt`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
if (row.mode === "title" && !row.text) errors.push({
|
||||||
|
message: `In Position ${row.pos} ist kein Titel hinterlegt`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
//if(row.mode === "text" && !row.text) errors.push({message: `In einer Freitext Position ist kein Titel hinterlegt`, type: "breaking"})
|
//if(row.mode === "text" && !row.text) errors.push({message: `In einer Freitext Position ist kein Titel hinterlegt`, type: "breaking"})
|
||||||
if(row.mode === "free" && !row.text) errors.push({message: `In einer freien Position ist kein Titel hinterlegt`, type: "breaking"})
|
if (row.mode === "free" && !row.text) errors.push({
|
||||||
|
message: `In einer freien Position ist kein Titel hinterlegt`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
|
||||||
if (["normal", "service", "free"].includes(row.mode)) {
|
if (["normal", "service", "free"].includes(row.mode)) {
|
||||||
|
|
||||||
if(!row.taxPercent && typeof row.taxPercent !== "number") errors.push({message: `In Position ${row.pos} ist kein Steuersatz hinterlegt`, type: "breaking"})
|
if (!row.taxPercent && typeof row.taxPercent !== "number") errors.push({
|
||||||
if(!row.price && typeof row.price !== "number") errors.push({message: `In Position ${row.pos} ist kein Preis hinterlegt`, type: "breaking"})
|
message: `In Position ${row.pos} ist kein Steuersatz hinterlegt`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
if (!row.price && typeof row.price !== "number") errors.push({
|
||||||
|
message: `In Position ${row.pos} ist kein Preis hinterlegt`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
if (!row.unit) errors.push({message: `In Position ${row.pos} ist keine Einheit hinterlegt`, type: "breaking"})
|
if (!row.unit) errors.push({message: `In Position ${row.pos} ist keine Einheit hinterlegt`, type: "breaking"})
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -691,13 +710,23 @@ const findDocumentErrors = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (itemInfo.value.type === "serialInvoices") {
|
if (itemInfo.value.type === "serialInvoices") {
|
||||||
if(!itemInfo.value.serialConfig.intervall) errors.push({message: `Kein Intervall für die Ausführung festgelegt`, type: "breaking"})
|
if (!itemInfo.value.serialConfig.intervall) errors.push({
|
||||||
if(!itemInfo.value.serialConfig.dateDirection) errors.push({message: `Kein Richtung für die Datierung festgelegt`, type: "breaking"})
|
message: `Kein Intervall für die Ausführung festgelegt`,
|
||||||
if(!itemInfo.value.serialConfig.firstExecution) errors.push({message: `Kein Datum für die erste Ausführung festgelegt`, type: "breaking"})
|
type: "breaking"
|
||||||
if(!itemInfo.value.serialConfig.executionUntil) errors.push({message: `Kein Datum für die letzte Ausführung festgelegt`, type: "info"})
|
})
|
||||||
|
if (!itemInfo.value.serialConfig.dateDirection) errors.push({
|
||||||
|
message: `Kein Richtung für die Datierung festgelegt`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
if (!itemInfo.value.serialConfig.firstExecution) errors.push({
|
||||||
|
message: `Kein Datum für die erste Ausführung festgelegt`,
|
||||||
|
type: "breaking"
|
||||||
|
})
|
||||||
|
if (!itemInfo.value.serialConfig.executionUntil) errors.push({
|
||||||
|
message: `Kein Datum für die letzte Ausführung festgelegt`,
|
||||||
|
type: "info"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -774,8 +803,6 @@ const documentTotal = computed(() => {
|
|||||||
console.log(titleSumsTransfer)
|
console.log(titleSumsTransfer)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
|
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
|
||||||
|
|
||||||
let totalGrossAlreadyPaid = 0
|
let totalGrossAlreadyPaid = 0
|
||||||
@@ -802,13 +829,6 @@ const documentTotal = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
titleSums: titleSums,
|
titleSums: titleSums,
|
||||||
titleSumsTransfer: titleSumsTransfer,
|
titleSumsTransfer: titleSumsTransfer,
|
||||||
@@ -868,7 +888,6 @@ const documentReport = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
//totalHoursSellingPrice += service.sellingPriceComposed.totalWorker * row.quantity
|
//totalHoursSellingPrice += service.sellingPriceComposed.totalWorker * row.quantity
|
||||||
@@ -961,13 +980,13 @@ const processDieselPosition = () => {
|
|||||||
itemInfo.value.agriculture = {...itemInfo.value.agriculture, ...agricultureData}
|
itemInfo.value.agriculture = {...itemInfo.value.agriculture, ...agricultureData}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDocumentData = () => {
|
const getDocumentData = async () => {
|
||||||
|
|
||||||
let customerData = customers.value.find(i => i.id === itemInfo.value.customer)
|
let customerData = customers.value.find(i => i.id === itemInfo.value.customer)
|
||||||
let contactData = dataStore.getContactById(itemInfo.value.contact)
|
let contactData = contacts.value.find(i => i.id === itemInfo.value.contact)
|
||||||
let businessInfo = profileStore.ownTenant.businessInfo
|
let businessInfo = auth.activeTenantData.businessInfo
|
||||||
|
|
||||||
if(profileStore.ownTenant.extraModules.includes("agriculture")) {
|
if (auth.activeTenantData.extraModules.includes("agriculture")) {
|
||||||
itemInfo.value.rows.forEach(row => {
|
itemInfo.value.rows.forEach(row => {
|
||||||
if (row.agriculture && row.agriculture.dieselUsage) {
|
if (row.agriculture && row.agriculture.dieselUsage) {
|
||||||
row.agriculture.description = `${row.agriculture.dieselUsage} L Diesel zu ${renderCurrency(row.agriculture.dieselPrice)}/L verbraucht ${row.description ? "\n" + row.description : ""}`
|
row.agriculture.description = `${row.agriculture.dieselUsage} L Diesel zu ${renderCurrency(row.agriculture.dieselPrice)}/L verbraucht ${row.description ? "\n" + row.description : ""}`
|
||||||
@@ -989,7 +1008,7 @@ const getDocumentData = () => {
|
|||||||
|
|
||||||
rows = itemInfo.value.rows.map(row => {
|
rows = itemInfo.value.rows.map(row => {
|
||||||
|
|
||||||
let unit = dataStore.units.find(i => i.id === row.unit)
|
let unit = units.value.find(i => i.id === row.unit)
|
||||||
|
|
||||||
if (!['pagebreak', 'title'].includes(row.mode)) {
|
if (!['pagebreak', 'title'].includes(row.mode)) {
|
||||||
if (row.agriculture && row.agriculture.description) {
|
if (row.agriculture && row.agriculture.description) {
|
||||||
@@ -1039,7 +1058,9 @@ const getDocumentData = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let contactPerson = profileStore.getProfileById(itemInfo.value.contactPerson)
|
let contactPerson = (await useNuxtApp().$api(`/api/user/${itemInfo.value.created_by}`, {
|
||||||
|
method: "GET"
|
||||||
|
})).profile
|
||||||
|
|
||||||
let returnTitleSums = {}
|
let returnTitleSums = {}
|
||||||
if (Object.keys(documentTotal.value.titleSums).length > 0) {
|
if (Object.keys(documentTotal.value.titleSums).length > 0) {
|
||||||
@@ -1115,7 +1136,7 @@ const getDocumentData = () => {
|
|||||||
},*/
|
},*/
|
||||||
{
|
{
|
||||||
label: "Ansprechpartner",
|
label: "Ansprechpartner",
|
||||||
content: contactPerson.fullName,
|
content: contactPerson.full_name,
|
||||||
},
|
},
|
||||||
...contactPerson.fixedTel || contactPerson.mobileTel ? [{
|
...contactPerson.fixedTel || contactPerson.mobileTel ? [{
|
||||||
label: "Telefon",
|
label: "Telefon",
|
||||||
@@ -1144,15 +1165,15 @@ const getDocumentData = () => {
|
|||||||
label: "Nettobetrag",
|
label: "Nettobetrag",
|
||||||
content: renderCurrency(documentTotal.value.totalNet),
|
content: renderCurrency(documentTotal.value.totalNet),
|
||||||
},
|
},
|
||||||
... rows.find(i => i.taxPercent === 19) ? [{
|
...rows.find(i => i.taxPercent === 19) && !["13b UStG"].includes(itemInfo.value.taxType) ? [{
|
||||||
label: `zzgl. 19% USt auf ${renderCurrency(documentTotal.value.totalNet19)}`,
|
label: `zzgl. 19% USt auf ${renderCurrency(documentTotal.value.totalNet19)}`,
|
||||||
content: renderCurrency(documentTotal.value.total19),
|
content: renderCurrency(documentTotal.value.total19),
|
||||||
}] : [],
|
}] : [],
|
||||||
... rows.find(i => i.taxPercent === 7) ? [{
|
...rows.find(i => i.taxPercent === 7) && !["13b UStG"].includes(itemInfo.value.taxType) ? [{
|
||||||
label: `zzgl. 7% USt auf ${renderCurrency(documentTotal.value.totalNet7)}`,
|
label: `zzgl. 7% USt auf ${renderCurrency(documentTotal.value.totalNet7)}`,
|
||||||
content: renderCurrency(documentTotal.value.total7),
|
content: renderCurrency(documentTotal.value.total7),
|
||||||
}] : [],
|
}] : [],
|
||||||
...rows.find(i => i.taxPercent === 0) ? [{
|
...rows.find(i => i.taxPercent === 0) && !["13b UStG"].includes(itemInfo.value.taxType) ? [{
|
||||||
label: `zzgl. 0% USt auf ${renderCurrency(documentTotal.value.totalNet0)}`,
|
label: `zzgl. 0% USt auf ${renderCurrency(documentTotal.value.totalNet0)}`,
|
||||||
content: renderCurrency(documentTotal.value.total0),
|
content: renderCurrency(documentTotal.value.total0),
|
||||||
}] : [],
|
}] : [],
|
||||||
@@ -1185,34 +1206,19 @@ const getDocumentData = () => {
|
|||||||
const showDocument = ref(false)
|
const showDocument = ref(false)
|
||||||
const uri = ref("")
|
const uri = ref("")
|
||||||
const generateDocument = async () => {
|
const generateDocument = async () => {
|
||||||
|
showDocument.value = false
|
||||||
const path = letterheads.value.find(i => i.id === itemInfo.value.letterhead).path
|
const path = letterheads.value.find(i => i.id === itemInfo.value.letterhead).path
|
||||||
|
|
||||||
/*const {data,error} = await supabase.functions.invoke('create_pdf',{
|
uri.value = await useFunctions().useCreatePDF(await getDocumentData(), path)
|
||||||
|
/*uri.value = await useNuxtApp().$api("/api/functions/createinvoicepdf",{
|
||||||
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
invoiceData: getDocumentData(),
|
invoiceData: await getDocumentData(),
|
||||||
backgroundPath: path,
|
backgroundPath: path
|
||||||
returnMode: "base64"
|
|
||||||
}
|
}
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
uri.value = await useFunctions().useCreatePDF(getDocumentData(), path)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//const {data,error} = await supabase.storage.from("files").download(path)
|
|
||||||
|
|
||||||
//console.log(data)
|
|
||||||
//console.log(error)
|
|
||||||
|
|
||||||
//console.log(JSON.stringify(getDocumentData()))
|
|
||||||
|
|
||||||
//uri.value = `data:${data.mimeType};base64,${data.base64}`
|
|
||||||
|
|
||||||
//uri.value = await useCreatePdf(getDocumentData(), await data.arrayBuffer())
|
|
||||||
//alert(uri.value)
|
|
||||||
showDocument.value = true
|
showDocument.value = true
|
||||||
//console.log(uri.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChangeTab = (index) => {
|
const onChangeTab = (index) => {
|
||||||
@@ -1266,9 +1272,9 @@ const saveSerialInvoice = async () => {
|
|||||||
let data = null
|
let data = null
|
||||||
|
|
||||||
if (route.params.id) {
|
if (route.params.id) {
|
||||||
data = await dataStore.updateItem("createddocuments", {...createData, id: itemInfo.value.id})
|
data = await useEntities("createddocuments").update(route.params.id, createData)
|
||||||
} else {
|
} else {
|
||||||
data = await dataStore.createNewItem("createddocuments", createData)
|
data = await useEntities("createddocuments").create(createData)
|
||||||
}
|
}
|
||||||
|
|
||||||
await router.push(`/createDocument/edit/${data.id}`)
|
await router.push(`/createDocument/edit/${data.id}`)
|
||||||
@@ -1297,11 +1303,9 @@ const saveDocument = async (state,resetup = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(profileStore.ownTenant.extraModules.includes("agriculture")) {
|
if (auth.activeTenantData.extraModules.includes("agriculture")) {
|
||||||
itemInfo.value.rows.forEach(row => {
|
itemInfo.value.rows.forEach(row => {
|
||||||
if (row.agriculture && row.agriculture.dieselUsage) {
|
if (row.agriculture && row.agriculture.dieselUsage) {
|
||||||
row.agriculture.description = `${row.agriculture.dieselUsage} L Diesel zu ${renderCurrency(row.agriculture.dieselPrice)}/L verbraucht ${row.description ? "\n" + row.description : ""}`
|
row.agriculture.description = `${row.agriculture.dieselUsage} L Diesel zu ${renderCurrency(row.agriculture.dieselPrice)}/L verbraucht ${row.description ? "\n" + row.description : ""}`
|
||||||
@@ -1343,6 +1347,7 @@ const saveDocument = async (state,resetup = false) => {
|
|||||||
deliveryDateType: itemInfo.value.deliveryDateType,
|
deliveryDateType: itemInfo.value.deliveryDateType,
|
||||||
info: {},
|
info: {},
|
||||||
createdBy: itemInfo.value.createdBy,
|
createdBy: itemInfo.value.createdBy,
|
||||||
|
created_by: itemInfo.value.created_by,
|
||||||
title: itemInfo.value.title,
|
title: itemInfo.value.title,
|
||||||
description: itemInfo.value.description,
|
description: itemInfo.value.description,
|
||||||
startText: itemInfo.value.startText,
|
startText: itemInfo.value.startText,
|
||||||
@@ -1358,9 +1363,10 @@ const saveDocument = async (state,resetup = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (route.params.id) {
|
if (route.params.id) {
|
||||||
await dataStore.updateItem("createddocuments", {...createData, id: itemInfo.value.id})
|
//await dataStore.updateItem("createddocuments", {...createData, id: itemInfo.value.id})
|
||||||
|
await useEntities("createddocuments").update(itemInfo.value.id, {...createData, id: itemInfo.value.id})
|
||||||
} else {
|
} else {
|
||||||
const data = await dataStore.createNewItem("createddocuments", createData)
|
const data = await useEntities("createddocuments").create(createData)
|
||||||
console.log(data)
|
console.log(data)
|
||||||
await router.push(`/createDocument/edit/${data.id}`)
|
await router.push(`/createDocument/edit/${data.id}`)
|
||||||
}
|
}
|
||||||
@@ -1368,8 +1374,14 @@ const saveDocument = async (state,resetup = false) => {
|
|||||||
if (resetup) await setupPage()
|
if (resetup) await setupPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedTab = ref(0)
|
||||||
|
|
||||||
const closeDocument = async () => {
|
const closeDocument = async () => {
|
||||||
|
|
||||||
|
if(selectedTab.value === 0) {
|
||||||
|
await generateDocument()
|
||||||
|
selectedTab.value = 1
|
||||||
|
} else {
|
||||||
loaded.value = false
|
loaded.value = false
|
||||||
|
|
||||||
await saveDocument("Gebucht")
|
await saveDocument("Gebucht")
|
||||||
@@ -1387,9 +1399,12 @@ const closeDocument = async () => {
|
|||||||
mappedType = "invoices"
|
mappedType = "invoices"
|
||||||
}
|
}
|
||||||
|
|
||||||
fileData.folder = (await supabase.from("folders").select("id").eq("tenant", profileStore.currentTenant).eq("function", mappedType).eq("year",dayjs().format("YYYY")).single()).data.id
|
const folders = await useEntities("folders").select()
|
||||||
|
console.log(folders)
|
||||||
|
fileData.folder = folders.find(i => i.function === mappedType && i.year === Number(dayjs().format("YYYY"))).id
|
||||||
|
|
||||||
let tag = (await supabase.from("filetags").select("id").eq("tenant", profileStore.currentTenant).eq("createddocumenttype", mappedType).single()).data
|
const tags = await useEntities("filetags").select()
|
||||||
|
fileData.type = tags.find(i => i.createddocumenttype === mappedType).id
|
||||||
|
|
||||||
function dataURLtoFile(dataurl, filename) {
|
function dataURLtoFile(dataurl, filename) {
|
||||||
var arr = dataurl.split(","),
|
var arr = dataurl.split(","),
|
||||||
@@ -1405,9 +1420,13 @@ const closeDocument = async () => {
|
|||||||
|
|
||||||
let file = dataURLtoFile(uri.value, `${itemInfo.value.documentNumber}.pdf`)
|
let file = dataURLtoFile(uri.value, `${itemInfo.value.documentNumber}.pdf`)
|
||||||
|
|
||||||
await dataStore.uploadFiles(fileData, [file],[tag.id], true)
|
await useFiles().uploadFiles(fileData, [file])
|
||||||
|
|
||||||
await router.push(`/createDocument/show/${itemInfo.value.id}`)
|
await router.push(`/createDocument/show/${itemInfo.value.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1454,12 +1473,12 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
console.log("Set Row Data")
|
console.log("Set Row Data")
|
||||||
if (service && service.id) {
|
if (service && service.id) {
|
||||||
row.service = service.id
|
row.service = service.id
|
||||||
services.value = await useSupabaseSelect("services","*")
|
services.value = await useEntities("services").select("*")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (product && product.id) {
|
if (product && product.id) {
|
||||||
row.product = product.id
|
row.product = product.id
|
||||||
product.value = await useSupabaseSelect("products","*")
|
products.value = await useEntities("products").select("*")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.service) {
|
if (row.service) {
|
||||||
@@ -1506,21 +1525,14 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
</UButton>
|
</UButton>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<ButtonWithConfirm
|
<ArchiveButton
|
||||||
v-if="itemInfo.state === 'Entwurf'"
|
|
||||||
color="rose"
|
color="rose"
|
||||||
|
type="createddocuments"
|
||||||
|
v-if="itemInfo.state === 'Entwurf' || itemInfo.type === 'serialInvoices'"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@confirmed="dataStore.updateItem('createddocuments',{id:itemInfo.id,archived: true}),
|
@confirmed="useEntities('createddocuments').update(itemInfo.id,{archived: true}),
|
||||||
router.push('/createDocument')"
|
router.push('/')"
|
||||||
>
|
/>
|
||||||
<template #button>
|
|
||||||
Archivieren
|
|
||||||
</template>
|
|
||||||
<template #header>
|
|
||||||
<span class="text-md dark:text-whitetext-black font-bold">Archivieren bestätigen</span>
|
|
||||||
</template>
|
|
||||||
Möchten Sie diesen Ausgangsbeleg wirklich archivieren?
|
|
||||||
</ButtonWithConfirm>
|
|
||||||
<UButton
|
<UButton
|
||||||
icon="i-mdi-content-save"
|
icon="i-mdi-content-save"
|
||||||
@click="saveDocument('Entwurf',true)"
|
@click="saveDocument('Entwurf',true)"
|
||||||
@@ -1533,7 +1545,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
@click="closeDocument"
|
@click="closeDocument"
|
||||||
v-if="itemInfo.id && itemInfo.type !== 'serialInvoices'"
|
v-if="itemInfo.id && itemInfo.type !== 'serialInvoices'"
|
||||||
>
|
>
|
||||||
Fertigstellen
|
{{selectedTab === 0 ? "Vorschau zeigen" : "Fertigstellen"}}
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton
|
<UButton
|
||||||
icon="i-mdi-content-save"
|
icon="i-mdi-content-save"
|
||||||
@@ -1545,7 +1557,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
<UDashboardPanelContent>
|
<UDashboardPanelContent>
|
||||||
<UTabs class="p-5" :items="tabItems" @change="onChangeTab" v-if="loaded">
|
<UTabs class="p-5" :items="tabItems" @change="onChangeTab" v-if="loaded" v-model="selectedTab">
|
||||||
<template #item="{item}">
|
<template #item="{item}">
|
||||||
<div v-if="item.label === 'Editor'">
|
<div v-if="item.label === 'Editor'">
|
||||||
|
|
||||||
@@ -1558,7 +1570,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
<template #description>
|
<template #description>
|
||||||
<ul class="list-disc ml-5">
|
<ul class="list-disc ml-5">
|
||||||
<li v-for="error in findDocumentErrors" :class="[...error.type === 'breaking' ? ['text-rose-600'] : ['dark:text-white','text-black']]">
|
<li v-for="error in findDocumentErrors"
|
||||||
|
:class="[...error.type === 'breaking' ? ['text-rose-600'] : ['dark:text-white','text-black']]">
|
||||||
{{ error.message }}
|
{{ error.message }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -1634,7 +1647,6 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</UCard>
|
</UCard>
|
||||||
|
|
||||||
</USlideover>
|
</USlideover>
|
||||||
@@ -1700,7 +1712,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
{{ customers.find(i => i.id === itemInfo.customer) ? customers.find(i => i.id === itemInfo.customer).name : "Kein Kunde ausgewählt" }}
|
{{ customers.find(i => i.id === itemInfo.customer) ? customers.find(i => i.id === itemInfo.customer).name : "Kein Kunde ausgewählt" }}
|
||||||
|
|
||||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="['transform rotate-90']" />
|
<UIcon name="i-heroicons-chevron-right-20-solid"
|
||||||
|
class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500"
|
||||||
|
:class="['transform rotate-90']"/>
|
||||||
</UButton>
|
</UButton>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<EntityModalButtons
|
<EntityModalButtons
|
||||||
@@ -1749,7 +1763,6 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<UAlert
|
<UAlert
|
||||||
v-if="customers.find(i => i.id === itemInfo.customer)"
|
v-if="customers.find(i => i.id === itemInfo.customer)"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
@@ -1787,10 +1800,13 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
:disabled="!itemInfo.customer"
|
:disabled="!itemInfo.customer"
|
||||||
>
|
>
|
||||||
<span class="truncate">{{itemInfo.contact ? contacts.find(i => i.id === itemInfo.contact).fullName : "Kein Kontakt ausgewählt"}}</span>
|
<span
|
||||||
|
class="truncate">{{ itemInfo.contact ? contacts.find(i => i.id === itemInfo.contact).fullName : "Kein Kontakt ausgewählt" }}</span>
|
||||||
|
|
||||||
|
|
||||||
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="['transform rotate-90']" />
|
<UIcon name="i-heroicons-chevron-right-20-solid"
|
||||||
|
class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500"
|
||||||
|
:class="['transform rotate-90']"/>
|
||||||
</UButton>
|
</UButton>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{ itemInfo.contact ? contacts.find(i => i.id === itemInfo.contact).fullName : "Kein Kontakt ausgewählt" }}
|
{{ itemInfo.contact ? contacts.find(i => i.id === itemInfo.contact).fullName : "Kein Kontakt ausgewählt" }}
|
||||||
@@ -1953,9 +1969,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
label="Ansprechpartner:"
|
label="Ansprechpartner:"
|
||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="profileStore.profiles"
|
:options="tenantUsers"
|
||||||
v-model="itemInfo.contactPerson"
|
v-model="itemInfo.created_by"
|
||||||
option-attribute="fullName"
|
option-attribute="full_name"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
@change="setContactPersonData"
|
@change="setContactPersonData"
|
||||||
/>
|
/>
|
||||||
@@ -1988,7 +2004,6 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
:search-attributes="['name']"
|
:search-attributes="['name']"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:disabled="!itemInfo.customer"
|
:disabled="!itemInfo.customer"
|
||||||
@change="checkForOpenAdvanceInvoices"
|
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{ plants.find(i => i.id === itemInfo.plant) ? plants.find(i => i.id === itemInfo.plant).name : "Kein Objekt ausgewählt" }}
|
{{ plants.find(i => i.id === itemInfo.plant) ? plants.find(i => i.id === itemInfo.plant).name : "Kein Objekt ausgewählt" }}
|
||||||
@@ -2027,13 +2042,13 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
:search-attributes="['name']"
|
:search-attributes="['name']"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:disabled="!itemInfo.customer"
|
:disabled="!itemInfo.customer"
|
||||||
@change="checkForOpenAdvanceInvoices"
|
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{ itemInfo.project ? projects.find(i => i.id === itemInfo.project).name : "Kein Projekt ausgewählt" }}
|
{{ itemInfo.project ? projects.find(i => i.id === itemInfo.project).name : "Kein Projekt ausgewählt" }}
|
||||||
</template>
|
</template>
|
||||||
<template #option="{option: project}">
|
<template #option="{option: project}">
|
||||||
{{dataStore.getCustomerById(project.customer).name}} - {{project.name}}
|
{{ customers.find(i => i.id === project.customer) ? customers.find(i => i.id === project.customer).name : "" }}
|
||||||
|
- {{ project.name }}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<UButton
|
<UButton
|
||||||
@@ -2162,7 +2177,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
{{ option.name }} - {{ option.text }}
|
{{ option.name }} - {{ option.text }}
|
||||||
</template>
|
</template>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? dataStore.texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert"}}
|
{{ texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert" }}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
@@ -2235,7 +2250,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
v-if="!['pagebreak','text'].includes(row.mode)"
|
v-if="!['pagebreak','text'].includes(row.mode)"
|
||||||
>{{row.pos}}</td>
|
>{{ row.pos }}
|
||||||
|
</td>
|
||||||
<td
|
<td
|
||||||
class="w-120"
|
class="w-120"
|
||||||
v-if="row.mode === 'free'"
|
v-if="row.mode === 'free'"
|
||||||
@@ -2266,7 +2282,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
@change="setRowData(row)"
|
@change="setRowData(row)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="truncate">{{products.find(i => i.id === row.product) ? products.find(i => i.id === row.product).name : "Kein Produkt ausgewählt" }}</span>
|
<span class="truncate">{{
|
||||||
|
products.find(i => i.id === row.product) ? products.find(i => i.id === row.product).name : "Kein Produkt ausgewählt"
|
||||||
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<EntityModalButtons
|
<EntityModalButtons
|
||||||
@@ -2336,7 +2354,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
@change="setRowData(row)"
|
@change="setRowData(row)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="truncate">{{services.find(i => i.id === row.service) ? services.find(i => i.id === row.service).name : "Keine Leistung ausgewählt" }}</span>
|
<span class="truncate">{{
|
||||||
|
services.find(i => i.id === row.service) ? services.find(i => i.id === row.service).name : "Keine Leistung ausgewählt"
|
||||||
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<EntityModalButtons
|
<EntityModalButtons
|
||||||
@@ -2392,7 +2412,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
v-model="row.quantity"
|
v-model="row.quantity"
|
||||||
:disabled="itemInfo.type === 'cancellationInvoices'"
|
:disabled="itemInfo.type === 'cancellationInvoices'"
|
||||||
type="number"
|
type="number"
|
||||||
:step="dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).step : '1' "
|
:step="units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).step : '1' "
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
@@ -2403,12 +2423,12 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="row.unit"
|
v-model="row.unit"
|
||||||
:disabled="itemInfo.type === 'cancellationInvoices'"
|
:disabled="itemInfo.type === 'cancellationInvoices'"
|
||||||
:options="dataStore.units"
|
:options="units"
|
||||||
option-attribute="name"
|
option-attribute="name"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).name : "Keine Einheit gewählt"}}
|
{{ units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).name : "Keine Einheit gewählt" }}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</td>
|
</td>
|
||||||
@@ -2503,7 +2523,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||||
Zeile bearbeiten
|
Zeile bearbeiten
|
||||||
</h3>
|
</h3>
|
||||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="row.showEdit = false" />
|
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1"
|
||||||
|
@click="row.showEdit = false"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
@@ -2514,7 +2535,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
<UInput
|
<UInput
|
||||||
v-model="row.quantity"
|
v-model="row.quantity"
|
||||||
type="number"
|
type="number"
|
||||||
:step="dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).step : '1' "
|
:step="units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).step : '1' "
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
@@ -2524,13 +2545,13 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="row.unit"
|
v-model="row.unit"
|
||||||
:options="dataStore.units"
|
:options="units"
|
||||||
option-attribute="name"
|
option-attribute="name"
|
||||||
|
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).name : "Keine Einheit gewählt"}}
|
{{ units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).name : "Keine Einheit gewählt" }}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
@@ -2990,7 +3011,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
{{option.name}} - {{option.text}}
|
{{option.name}} - {{option.text}}
|
||||||
</template>
|
</template>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? dataStore.texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert"}}
|
{{texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert"}}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
@@ -3011,12 +3032,17 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
Show
|
Show
|
||||||
</UButton>-->
|
</UButton>-->
|
||||||
<object
|
<PDFViewer
|
||||||
|
v-if="showDocument"
|
||||||
|
:uri="uri"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- <object
|
||||||
:data="uri"
|
:data="uri"
|
||||||
v-if="showDocument"
|
v-if="showDocument"
|
||||||
type="application/pdf"
|
type="application/pdf"
|
||||||
class="w-full previewDocumentMobile"
|
class="w-full previewDocumentMobile"
|
||||||
/>
|
/>-->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -30,26 +30,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
<UDashboardToolbar>
|
<UDashboardToolbar>
|
||||||
<template #left>
|
|
||||||
<!-- <UCheckbox
|
|
||||||
v-model="showDrafts"
|
|
||||||
label="Entwürfe Anzeigen"
|
|
||||||
class="my-auto mr-3"
|
|
||||||
/>-->
|
|
||||||
<USelectMenu
|
|
||||||
v-model="selectedTypes"
|
|
||||||
icon="i-heroicons-adjustments-horizontal-solid"
|
|
||||||
:options="templateTypes"
|
|
||||||
multiple
|
|
||||||
class="hidden lg:block"
|
|
||||||
by="key"
|
|
||||||
@change="tempStore.modifyFilter('createddocuments',selectedTypes)"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
Typ
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #right>
|
<template #right>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
@@ -66,10 +47,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
|
v-if="selectableFilters.length > 0"
|
||||||
icon="i-heroicons-adjustments-horizontal-solid"
|
icon="i-heroicons-adjustments-horizontal-solid"
|
||||||
multiple
|
multiple
|
||||||
v-model="selectedFilters"
|
v-model="selectedFilters"
|
||||||
:options="['Nur offene anzeigen']"
|
:options="selectableFilters"
|
||||||
:color="selectedFilters.length > 0 ? 'primary' : 'white'"
|
:color="selectedFilters.length > 0 ? 'primary' : 'white'"
|
||||||
:ui-menu="{ width: 'min-w-max' }"
|
:ui-menu="{ width: 'min-w-max' }"
|
||||||
>
|
>
|
||||||
@@ -79,8 +61,16 @@
|
|||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardToolbar>
|
</UDashboardToolbar>
|
||||||
|
|
||||||
<UTabs :items="selectedTypes" class="m-3">
|
<UTabs :items="selectedTypes" class="m-3">
|
||||||
|
<template #default="{item}">
|
||||||
|
{{item.label}}
|
||||||
|
<UBadge
|
||||||
|
variant="outline"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
{{filteredRows.filter(i => item.key === 'invoices' ? ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type) : item.key === i.type).length}}
|
||||||
|
</UBadge>
|
||||||
|
</template>
|
||||||
<template #item="{item}">
|
<template #item="{item}">
|
||||||
<div style="height: 80vh; overflow-y: scroll">
|
<div style="height: 80vh; overflow-y: scroll">
|
||||||
<UTable
|
<UTable
|
||||||
@@ -128,8 +118,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template #partner-data="{row}">
|
<template #partner-data="{row}">
|
||||||
<span v-if="row.customer">{{row.customer ? row.customer.name : ""}}</span>
|
<span v-if="row.customer && row.customer.name.length <21">{{row.customer ? row.customer.name : ""}}</span>
|
||||||
|
<UTooltip v-else-if="row.customer && row.customer.name.length > 20" :text="row.customer.name">
|
||||||
|
{{row.customer.name.substring(0,20)}}...
|
||||||
|
</UTooltip>
|
||||||
</template>
|
</template>
|
||||||
<template #reference-data="{row}">
|
<template #reference-data="{row}">
|
||||||
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.documentNumber}}</span>
|
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.documentNumber}}</span>
|
||||||
@@ -165,10 +157,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'/': () => {
|
'/': () => {
|
||||||
//console.log(searchinput)
|
//console.log(searchinput)
|
||||||
@@ -204,11 +192,15 @@ const dataStore = useDataStore()
|
|||||||
const tempStore = useTempStore()
|
const tempStore = useTempStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const type = "createddocuments"
|
||||||
|
const dataType = dataStore.dataTypes[type]
|
||||||
|
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const selectedItem = ref(0)
|
const selectedItem = ref(0)
|
||||||
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
items.value = (await useSupabaseSelect("createddocuments","*, customer(id,name), statementallocations(id,amount),linkedDocument(*)","documentNumber")).filter(i => !i.archived)
|
items.value = (await useEntities("createddocuments").select("*, customer(id,name), statementallocations(id,amount),linkedDocument(*)","documentNumber",true, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
@@ -216,47 +208,38 @@ setupPage()
|
|||||||
const templateColumns = [
|
const templateColumns = [
|
||||||
{
|
{
|
||||||
key: "reference",
|
key: "reference",
|
||||||
label: "Referenz",
|
label: "Referenz"
|
||||||
sortable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'type',
|
key: 'type',
|
||||||
label: "Typ",
|
label: "Typ"
|
||||||
sortable: true
|
|
||||||
}, {
|
}, {
|
||||||
key: 'state',
|
key: 'state',
|
||||||
label: "Status",
|
label: "Status"
|
||||||
sortable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "amount",
|
key: "amount",
|
||||||
label: "Betrag",
|
label: "Betrag"
|
||||||
sortable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'partner',
|
key: 'partner',
|
||||||
label: "Kunde",
|
label: "Kunde"
|
||||||
sortable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "date",
|
key: "date",
|
||||||
label: "Datum",
|
label: "Datum"
|
||||||
sortable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "amountOpen",
|
key: "amountOpen",
|
||||||
label: "Offener Betrag",
|
label: "Offener Betrag"
|
||||||
sortable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "paid",
|
key: "paid",
|
||||||
label: "Bezahlt",
|
label: "Bezahlt"
|
||||||
sortable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "dueDate",
|
key: "dueDate",
|
||||||
label: "Fällig",
|
label: "Fällig"
|
||||||
sortable: true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const selectedColumns = ref(tempStore.columns["createddocuments"] ? tempStore.columns["createddocuments"] : templateColumns)
|
const selectedColumns = ref(tempStore.columns["createddocuments"] ? tempStore.columns["createddocuments"] : templateColumns)
|
||||||
@@ -309,24 +292,34 @@ const clearSearchString = () => {
|
|||||||
tempStore.clearSearchString('createddocuments')
|
tempStore.clearSearchString('createddocuments')
|
||||||
searchString.value = ''
|
searchString.value = ''
|
||||||
}
|
}
|
||||||
const selectedFilters = ref([])
|
const selectableFilters = ref(dataType.filters.map(i => i.name))
|
||||||
|
const selectedFilters = ref(dataType.filters.filter(i => i.default).map(i => i.name) || [])
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
|
|
||||||
let temp = items.value.filter(i => types.value.find(x => x.key === 'invoices' ? ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type) : x.key === i.type))
|
let tempItems = items.value.filter(i => types.value.find(x => x.key === 'invoices' ? ['invoices', 'advanceInvoices', 'cancellationInvoices'].includes(i.type) : x.key === i.type))
|
||||||
temp = temp.filter(i => i.type !== "serialInvoices")
|
tempItems = tempItems.filter(i => i.type !== "serialInvoices")
|
||||||
|
|
||||||
/*if(showDrafts.value === true) {
|
tempItems = tempItems.map(i => {
|
||||||
temp = temp.filter(i => i.state === "Entwurf")
|
return {
|
||||||
} else {
|
...i,
|
||||||
temp = temp.filter(i => i.state !== "Entwurf")
|
class: i.archived ? 'bg-red-500/50 dark:bg-red-400/50' : null
|
||||||
}*/
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if(selectedFilters.value.includes("Nur offene anzeigen")){
|
if(selectedFilters.value.length > 0) {
|
||||||
temp = temp.filter(i => !isPaid(i))
|
selectedFilters.value.forEach(filterName => {
|
||||||
|
let filter = dataType.filters.find(i => i.name === filterName)
|
||||||
|
tempItems = tempItems.filter(filter.filterFunction)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return useSearch(searchString.value, temp.slice().reverse())
|
|
||||||
|
tempItems = useSearch(searchString.value, tempItems)
|
||||||
|
|
||||||
|
|
||||||
|
return useSearch(searchString.value, tempItems.slice().reverse())
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -126,13 +126,20 @@ const items = ref([])
|
|||||||
const selectedItem = ref(0)
|
const selectedItem = ref(0)
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
items.value = await useSupabaseSelect("createddocuments","*, customer(id,name)","documentDate")
|
items.value = await useEntities("createddocuments").select("*, customer(id,name)","documentDate",undefined,true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchString = ref("")
|
const searchString = ref("")
|
||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
|
|
||||||
let temp = items.value.filter(i => i.type === "serialInvoices")
|
|
||||||
|
|
||||||
|
let temp = items.value.filter(i => i.type === "serialInvoices").map(i => {
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
class: i.archived ? 'bg-red-500/50 dark:bg-red-400/50' : null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/*if(showDrafts.value === true) {
|
/*if(showDrafts.value === true) {
|
||||||
temp = temp.filter(i => i.state === "Entwurf")
|
temp = temp.filter(i => i.state === "Entwurf")
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
|
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'backspace': () => {
|
'backspace': () => {
|
||||||
@@ -11,41 +9,29 @@ defineShortcuts({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const dataStore = useDataStore()
|
||||||
|
|
||||||
const itemInfo = ref({})
|
const itemInfo = ref({})
|
||||||
const linkedDocument =ref({})
|
const linkedDocument =ref({})
|
||||||
const currentTenant = ref({})
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
if(route.params) {
|
if(route.params) {
|
||||||
if(route.params.id) itemInfo.value = await useSupabaseSelectSingle("createddocuments",route.params.id,"*, files(*)")
|
if(route.params.id) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*,files(*),linkedDocument(*)")
|
||||||
|
|
||||||
console.log(itemInfo.value)
|
console.log(itemInfo.value)
|
||||||
|
|
||||||
linkedDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
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",profileStore.currentTenant).single()).data
|
|
||||||
console.log(currentTenant.value)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
|
|
||||||
const openEmail = () => {
|
const openEmail = () => {
|
||||||
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
|
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(auth.activeTenantData.standardEmailForInvoices || "")}`)
|
||||||
} else {
|
} else {
|
||||||
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]`)
|
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]`)
|
||||||
}
|
}
|
||||||
@@ -110,14 +96,27 @@ const openEmail = () => {
|
|||||||
>
|
>
|
||||||
Kunde
|
Kunde
|
||||||
</UButton>
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
v-if="itemInfo.linkedDocument"
|
||||||
|
@click="router.push(`/standardEntity/createDocument/show/${itemInfo.linkedDocument}`)"
|
||||||
|
icon="i-heroicons-link"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
{{dataStore.documentTypesForCreation[itemInfo.linkedDocument.type].labelSingle}} - {{itemInfo.linkedDocument.documentNumber}}
|
||||||
|
</UButton>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</UDashboardToolbar>
|
</UDashboardToolbar>
|
||||||
<UDashboardPanelContent>
|
<UDashboardPanelContent>
|
||||||
<object
|
<!-- <object
|
||||||
:data="linkedDocument.url"
|
:data="linkedDocument.url"
|
||||||
class="w-full previewDocumentMobile"
|
class="w-full previewDocumentMobile"
|
||||||
/>
|
/>-->
|
||||||
|
<PDFViewer v-if="linkedDocument.id" :file-id="linkedDocument.id" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</UDashboardPanelContent>
|
</UDashboardPanelContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
108
pages/createdletters/[mode]/[[id]].vue
Normal file
108
pages/createdletters/[mode]/[[id]].vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<script setup>
|
||||||
|
import {useFunctions} from "~/composables/useFunctions.js";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const profileStore = useProfileStore();
|
||||||
|
|
||||||
|
const preloadedContent = ref("")
|
||||||
|
const letterheads = ref([])
|
||||||
|
const itemInfo = ref({
|
||||||
|
contentHTML: "",
|
||||||
|
contentJSON: {},
|
||||||
|
contentText: ""
|
||||||
|
})
|
||||||
|
const showDocument = ref(false)
|
||||||
|
const uri = ref("")
|
||||||
|
|
||||||
|
const setupPage = async () => {
|
||||||
|
letterheads.value = await useSupabaseSelect("letterheads","*")
|
||||||
|
|
||||||
|
preloadedContent.value = `<p></p><p></p><p></p>`
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPage()
|
||||||
|
|
||||||
|
const onChangeTab = (index) => {
|
||||||
|
if(index === 1) {
|
||||||
|
generateDocument()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDocumentData = () => {
|
||||||
|
/*const returnData = {
|
||||||
|
adressLine: `${businessInfo.name}, ${businessInfo.street}, ${businessInfo.zip} ${businessInfo.city}`,
|
||||||
|
recipient: [
|
||||||
|
customerData.name,
|
||||||
|
... customerData.nameAddition ? [customerData.nameAddition] : [],
|
||||||
|
... contactData ? [`${contactData.firstName} ${contactData.lastName}`] : [],
|
||||||
|
itemInfo.value.address.street,
|
||||||
|
... itemInfo.value.address.special ? [itemInfo.value.address.special] : [],
|
||||||
|
`${itemInfo.value.address.zip} ${itemInfo.value.address.city}`,
|
||||||
|
|
||||||
|
],
|
||||||
|
}*/
|
||||||
|
|
||||||
|
const returnData = {
|
||||||
|
adressLine: `Federspiel Technology UG, Am Schwarzen Brack 14, 26452 Sande`,
|
||||||
|
recipient: [
|
||||||
|
"Federspiel Technology",
|
||||||
|
"UG haftungsbeschränkt",
|
||||||
|
"Florian Federspiel",
|
||||||
|
"Am Schwarzen Brack 14",
|
||||||
|
"Zusatz",
|
||||||
|
"26452 Sande",
|
||||||
|
],
|
||||||
|
contentJSON: itemInfo.value.contentJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateDocument = async () => {
|
||||||
|
const ownTenant = profileStore.ownTenant
|
||||||
|
const path = letterheads.value[0].path
|
||||||
|
|
||||||
|
uri.value = await useFunctions().useCreateLetterPDF(getDocumentData(), path)
|
||||||
|
|
||||||
|
showDocument.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentChanged = (content) => {
|
||||||
|
itemInfo.value.contentHTML = content.html
|
||||||
|
itemInfo.value.contentJSON = content.json
|
||||||
|
itemInfo.value.contentText = content.text
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UDashboardNavbar title="Anschreiben bearbeiten"/>
|
||||||
|
{{itemInfo}}
|
||||||
|
<UDashboardPanelContent>
|
||||||
|
<UTabs @change="onChangeTab" :items="[{label: 'Editor'},{label: 'Vorschau'}]">
|
||||||
|
<template #item="{item}">
|
||||||
|
<div v-if="item.label === 'Editor'">
|
||||||
|
<Tiptap
|
||||||
|
class="mt-3"
|
||||||
|
@updateContent="contentChanged"
|
||||||
|
:preloadedContent="preloadedContent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="item.label === 'Vorschau'">
|
||||||
|
<object
|
||||||
|
:data="uri"
|
||||||
|
v-if="showDocument"
|
||||||
|
type="application/pdf"
|
||||||
|
class="w-full previewDocumentMobile"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UTabs>
|
||||||
|
</UDashboardPanelContent>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.previewDocumentMobile {
|
||||||
|
aspect-ratio: 1 / 1.414;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
|
|
||||||
const selectedTab = ref(0)
|
|
||||||
const selectedMail = ref(null)
|
|
||||||
const selectedMailboxPath = ref("INBOX")
|
|
||||||
const emails = ref([])
|
|
||||||
const accountData = ref(null)
|
|
||||||
const availableAccounts = ref(null)
|
|
||||||
const selectedAccount = ref(null)
|
|
||||||
const setupPage = async () => {
|
|
||||||
availableAccounts.value = (await supabase.from("emailAccounts").select("*").contains("profiles",[profileStore.activeProfile.id])).data
|
|
||||||
console.log(availableAccounts.value)
|
|
||||||
|
|
||||||
if(availableAccounts.value.length > 0) {
|
|
||||||
selectedAccount.value = availableAccounts.value[0].id
|
|
||||||
accountData.value = await useSupabaseSelectSingle("emailAccounts", selectedAccount.value)
|
|
||||||
emails.value = await useSupabaseSelect("emailMessages", "*", "date", false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filteredMails = computed(() => {
|
|
||||||
|
|
||||||
let temp = emails.value.filter(i => i.mailboxPath.toLowerCase() === selectedMailboxPath.value.toLowerCase())
|
|
||||||
|
|
||||||
|
|
||||||
if(selectedTab.value === 0){
|
|
||||||
return temp
|
|
||||||
} else {
|
|
||||||
return temp.filter(i => !i.seen)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return emails.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const isMailPanelOpen = computed({
|
|
||||||
get() {
|
|
||||||
return !!selectedMail.value
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
if (!value) {
|
|
||||||
selectedMail.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const changeSeen = async (seen) => {
|
|
||||||
const {data,error} = await supabase.functions.invoke('emailcontrol',{
|
|
||||||
body: {
|
|
||||||
method: seen ? "makeSeen" : "makeUnseen",
|
|
||||||
emailId: selectedMail.value.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if(data) {
|
|
||||||
setupPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- <UDashboardNavbar title="E-Mails">
|
|
||||||
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardToolbar>
|
|
||||||
|
|
||||||
</UDashboardToolbar>-->
|
|
||||||
<UDashboardPage
|
|
||||||
v-if="selectedAccount"
|
|
||||||
>
|
|
||||||
<UDashboardPanel
|
|
||||||
id="inbox"
|
|
||||||
:width="400"
|
|
||||||
:resizable="{ min: 300, max: 500 }"
|
|
||||||
|
|
||||||
>
|
|
||||||
<UDashboardNavbar>
|
|
||||||
<template #left>
|
|
||||||
<USelectMenu
|
|
||||||
v-if="accountData"
|
|
||||||
class="w-40"
|
|
||||||
:options="accountData.mailboxes"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="path"
|
|
||||||
v-model="selectedMailboxPath"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-arrow-path"
|
|
||||||
variant="ghost"
|
|
||||||
color="gray"
|
|
||||||
@click="setupPage"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UTabs
|
|
||||||
v-model="selectedTab"
|
|
||||||
:items="[{label: 'All'}, {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"
|
|
||||||
@emailSelected=" !selectedMail.seen ? changeSeen(true): ''"
|
|
||||||
/>
|
|
||||||
</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="Ungelesen">
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-eye-slash"
|
|
||||||
color="gray"
|
|
||||||
variant="ghost"
|
|
||||||
@click="changeSeen(false)"
|
|
||||||
/>
|
|
||||||
</UTooltip>
|
|
||||||
<!-- <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>
|
|
||||||
</UDashboardPage>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex-1 flex-col hidden lg:flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<UIcon
|
|
||||||
name="i-heroicons-inbox"
|
|
||||||
class="w-32 h-32 text-gray-400 dark:text-gray-500"
|
|
||||||
/>
|
|
||||||
<span class="font-bold text-2xl">Kein E-Mail Account verfügbar</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <UInput
|
|
||||||
placeholder="Empfänger"
|
|
||||||
variant="ghost"
|
|
||||||
class="m-2"
|
|
||||||
/>
|
|
||||||
<UInput
|
|
||||||
placeholder="Betreff"
|
|
||||||
variant="ghost"
|
|
||||||
class="m-2"
|
|
||||||
/>
|
|
||||||
<UInput
|
|
||||||
placeholder="CC"
|
|
||||||
variant="ghost"
|
|
||||||
class="m-2"
|
|
||||||
/>
|
|
||||||
<UInput
|
|
||||||
placeholder="BCC"
|
|
||||||
variant="ghost"
|
|
||||||
class="m-2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="my-5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Tiptap/>-->
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
//TODO: BACKENDCHANGE EMAIL SENDING
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const emailData = ref({
|
const emailData = ref({
|
||||||
to:"",
|
to:"",
|
||||||
@@ -23,14 +23,16 @@ const loadedDocuments = ref([])
|
|||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
const noAccountsPresent = ref(false)
|
const noAccountsPresent = ref(false)
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
emailAccounts.value = await useSupabaseSelect("emailAccounts")
|
//emailAccounts.value = await useEntities("emailAccounts").select()
|
||||||
|
|
||||||
|
emailAccounts.value = await useNuxtApp().$api("/api/email/accounts")
|
||||||
|
|
||||||
if(emailAccounts.value.length === 0) {
|
if(emailAccounts.value.length === 0) {
|
||||||
noAccountsPresent.value = true
|
noAccountsPresent.value = true
|
||||||
} else {
|
} else {
|
||||||
emailData.value.account = emailAccounts.value[0].id
|
emailData.value.account = emailAccounts.value[0].id
|
||||||
|
|
||||||
preloadedContent.value = `<p></p><p></p><p></p>${profileStore.activeProfile.emailSignature || ""}`
|
preloadedContent.value = `<p></p><p></p><p></p>${auth.profile.email_signature || ""}`
|
||||||
|
|
||||||
//Check Query
|
//Check Query
|
||||||
if(route.query.to) emailData.value.to = route.query.to
|
if(route.query.to) emailData.value.to = route.query.to
|
||||||
@@ -44,15 +46,13 @@ const setupPage = async () => {
|
|||||||
const data = await useFiles().selectSomeDocuments(JSON.parse(route.query.loadDocuments))
|
const data = await useFiles().selectSomeDocuments(JSON.parse(route.query.loadDocuments))
|
||||||
console.log(data)
|
console.log(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(data) loadedDocuments.value = data
|
if(data) loadedDocuments.value = data
|
||||||
|
|
||||||
//console.log(loadedDocuments.value)
|
//console.log(loadedDocuments.value)
|
||||||
|
|
||||||
if(loadedDocuments.value.length > 0) {
|
if(loadedDocuments.value.length > 0) {
|
||||||
console.log(loadedDocuments.value[0])
|
console.log(loadedDocuments.value[0])
|
||||||
emailData.value.subject = loadedDocuments.value[0].createddocument.title
|
emailData.value.subject = `${loadedDocuments.value[0].createddocument.title} von ${auth.activeTenantData.businessInfo.name}`
|
||||||
|
|
||||||
if(loadedDocuments.value[0].createddocument.contact && loadedDocuments.value[0].createddocument.contact.email) {
|
if(loadedDocuments.value[0].createddocument.contact && loadedDocuments.value[0].createddocument.contact.email) {
|
||||||
console.log("Contact")
|
console.log("Contact")
|
||||||
@@ -63,7 +63,6 @@ const setupPage = async () => {
|
|||||||
emailData.value.to = loadedDocuments.value[0].createddocument.customer.infoData.email
|
emailData.value.to = loadedDocuments.value[0].createddocument.customer.infoData.email
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
@@ -132,25 +131,33 @@ const sendEmail = async () => {
|
|||||||
|
|
||||||
for await (const doc of loadedDocuments.value) {
|
for await (const doc of loadedDocuments.value) {
|
||||||
|
|
||||||
const {data,error} = await supabase.storage.from("filesdev").download(doc.path)
|
|
||||||
|
const res = await useFiles().downloadFile(doc.id, null, true)
|
||||||
|
|
||||||
body.attachments.push({
|
body.attachments.push({
|
||||||
filename: doc.path.split("/")[doc.path.split("/").length -1],
|
filename: doc.path.split("/")[doc.path.split("/").length -1],
|
||||||
content: await blobToBase64(data),
|
content: await blobToBase64(res),
|
||||||
contentType: data.type,
|
contentType: res.type,
|
||||||
encoding: "base64",
|
encoding: "base64",
|
||||||
contentDisposition: "attachment"
|
contentDisposition: "attachment"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase.functions.invoke('send_email', {
|
console.log(body)
|
||||||
body
|
|
||||||
|
const res = await useNuxtApp().$api("/api/email/send",{
|
||||||
|
method: "POST",
|
||||||
|
body: body,
|
||||||
})
|
})
|
||||||
if(error) {
|
|
||||||
|
console.log(res)
|
||||||
|
|
||||||
|
|
||||||
|
if(!res.success) {
|
||||||
toast.add({title: "Fehler beim Absenden der E-Mail", color: "rose"})
|
toast.add({title: "Fehler beim Absenden der E-Mail", color: "rose"})
|
||||||
|
|
||||||
} else if(data) {
|
} else {
|
||||||
router.push("/")
|
navigateTo("/")
|
||||||
toast.add({title: "E-Mail zum Senden eingereiht"})
|
toast.add({title: "E-Mail zum Senden eingereiht"})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +207,7 @@ const sendEmail = async () => {
|
|||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="emailAccounts"
|
:options="emailAccounts"
|
||||||
option-attribute="emailAddress"
|
option-attribute="email"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
v-model="emailData.account"
|
v-model="emailData.account"
|
||||||
/>
|
/>
|
||||||
|
|||||||
162
pages/export.vue
Normal file
162
pages/export.vue
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
const exports = ref([])
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const setupPage = async () => {
|
||||||
|
exports.value = await useNuxtApp().$api("/api/exports",{
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPage()
|
||||||
|
|
||||||
|
function downloadFile(row) {
|
||||||
|
const a = document.createElement("a")
|
||||||
|
a.href = row.url
|
||||||
|
a.download = row.file_path.split("/").pop()
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCreateExportModal = ref(false)
|
||||||
|
const createExportData = ref({
|
||||||
|
start_date: null,
|
||||||
|
end_date: null,
|
||||||
|
beraternr:null,
|
||||||
|
mandantennr: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const createExport = async () => {
|
||||||
|
const res = await useNuxtApp().$api("/api/exports/datev",{
|
||||||
|
method: "POST",
|
||||||
|
body: createExportData.value
|
||||||
|
})
|
||||||
|
|
||||||
|
showCreateExportModal.value = false
|
||||||
|
|
||||||
|
if(res.success) {
|
||||||
|
toast.add({title: "Export wird erstellt. Sie erhalten eine Benachrichtigung sobald es soweit ist."})
|
||||||
|
} else {
|
||||||
|
toast.add({title: "Es gab einen Fehler beim erstellen", color: "rose"})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UDashboardNavbar
|
||||||
|
title="Exporte"
|
||||||
|
>
|
||||||
|
<template #right>
|
||||||
|
<UButton
|
||||||
|
@click="showCreateExportModal = true"
|
||||||
|
>+ Export</UButton>
|
||||||
|
</template>
|
||||||
|
</UDashboardNavbar>
|
||||||
|
<UTable
|
||||||
|
:rows="exports"
|
||||||
|
:columns="[
|
||||||
|
{
|
||||||
|
key: 'created_at',
|
||||||
|
label: 'Erstellt am',
|
||||||
|
},{
|
||||||
|
key: 'start_date',
|
||||||
|
label: 'Start',
|
||||||
|
},{
|
||||||
|
key: 'end_date',
|
||||||
|
label: 'Ende',
|
||||||
|
},{
|
||||||
|
key: 'valid_until',
|
||||||
|
label: 'Gültig bis',
|
||||||
|
},{
|
||||||
|
key: 'type',
|
||||||
|
label: 'Typ',
|
||||||
|
},{
|
||||||
|
key: 'download',
|
||||||
|
label: 'Download',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template #created_at-data="{row}">
|
||||||
|
{{dayjs(row.created_at).format("DD.MM.YYYY HH:mm")}}
|
||||||
|
</template>
|
||||||
|
<template #start_date-data="{row}">
|
||||||
|
{{dayjs(row.start_date).format("DD.MM.YYYY HH:mm")}}
|
||||||
|
</template>
|
||||||
|
<template #end_date-data="{row}">
|
||||||
|
{{dayjs(row.end_date).format("DD.MM.YYYY HH:mm")}}
|
||||||
|
</template>
|
||||||
|
<template #valid_until-data="{row}">
|
||||||
|
{{dayjs(row.valid_until).format("DD.MM.YYYY HH:mm")}}
|
||||||
|
</template>
|
||||||
|
<template #download-data="{row}">
|
||||||
|
<UButton
|
||||||
|
@click="downloadFile(row)"
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
|
||||||
|
<UModal v-model="showCreateExportModal">
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
Export erstellen
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<UFormGroup
|
||||||
|
label="Start:"
|
||||||
|
>
|
||||||
|
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||||
|
<UButton
|
||||||
|
icon="i-heroicons-calendar-days-20-solid"
|
||||||
|
:label="createExportData.start_date ? dayjs(createExportData.start_date).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||||
|
variant="outline"
|
||||||
|
class="mx-auto"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<template #panel="{ close }">
|
||||||
|
<LazyDatePicker v-model="createExportData.start_date" @close="close" />
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
</UFormGroup>
|
||||||
|
|
||||||
|
<UFormGroup
|
||||||
|
label="Ende:"
|
||||||
|
>
|
||||||
|
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||||
|
<UButton
|
||||||
|
icon="i-heroicons-calendar-days-20-solid"
|
||||||
|
:label="createExportData.end_date ? dayjs(createExportData.end_date).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||||
|
variant="outline"
|
||||||
|
class="mx-auto"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<template #panel="{ close }">
|
||||||
|
<LazyDatePicker v-model="createExportData.end_date" @close="close" />
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
</UFormGroup>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<UButton
|
||||||
|
@click="createExport"
|
||||||
|
>
|
||||||
|
Erstellen
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</UCard>
|
||||||
|
</UModal>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -2,23 +2,14 @@
|
|||||||
|
|
||||||
|
|
||||||
import {BlobReader, BlobWriter, ZipWriter} from "@zip.js/zip.js";
|
import {BlobReader, BlobWriter, ZipWriter} from "@zip.js/zip.js";
|
||||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
|
||||||
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
||||||
import DocumentUploadModal from "~/components/DocumentUploadModal.vue";
|
import DocumentUploadModal from "~/components/DocumentUploadModal.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import arraySort from "array-sort";
|
import arraySort from "array-sort";
|
||||||
import {useTempStore} from "~/stores/temp.js";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
/*'/': () => {
|
|
||||||
//console.log(searchinput)
|
|
||||||
//searchinput.value.focus()
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},*/
|
|
||||||
'+': () => {
|
'+': () => {
|
||||||
//Hochladen
|
//Hochladen
|
||||||
uploadModalOpen.value = true
|
uploadModalOpen.value = true
|
||||||
@@ -30,9 +21,10 @@ defineShortcuts({
|
|||||||
|
|
||||||
if(entry.type === "file") {
|
if(entry.type === "file") {
|
||||||
showFile(entry.id)
|
showFile(entry.id)
|
||||||
console.log(entry)
|
} else if(createFolderModalOpen.value === false && entry.type === "folder") {
|
||||||
} else {
|
|
||||||
changeFolder(currentFolders.value.find(i => i.id === entry.id))
|
changeFolder(currentFolders.value.find(i => i.id === entry.id))
|
||||||
|
} else if(createFolderModalOpen.value === true) {
|
||||||
|
createFolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -55,13 +47,11 @@ defineShortcuts({
|
|||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const tempStore = useTempStore()
|
const tempStore = useTempStore()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
|
|
||||||
dataStore.fetchDocuments()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const uploadModalOpen = ref(false)
|
const uploadModalOpen = ref(false)
|
||||||
const createFolderModalOpen = ref(false)
|
const createFolderModalOpen = ref(false)
|
||||||
@@ -69,7 +59,7 @@ const uploadInProgress = ref(false)
|
|||||||
const fileUploadFormData = ref({
|
const fileUploadFormData = ref({
|
||||||
tags: ["Eingang"],
|
tags: ["Eingang"],
|
||||||
path: "",
|
path: "",
|
||||||
tenant: profileStore.currentTenant,
|
tenant: auth.activeTenant,
|
||||||
folder: null
|
folder: null
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -92,15 +82,16 @@ const isDragTarget = ref(false)
|
|||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
folders.value = await useSupabaseSelect("folders")
|
folders.value = await useEntities("folders").select()
|
||||||
|
|
||||||
documents.value = await files.selectDocuments()
|
documents.value = await files.selectDocuments()
|
||||||
|
|
||||||
filetags.value = await useSupabaseSelect("filetags")
|
filetags.value = await useEntities("filetags").select()
|
||||||
|
|
||||||
if(route.query) {
|
if(route.query) {
|
||||||
if(route.query.folder) {
|
if(route.query.folder) {
|
||||||
currentFolder.value = await useSupabaseSelectSingle("folders", route.query.folder)
|
currentFolder.value = await useEntities("folders").selectSingle(route.query.folder)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +109,6 @@ const setupPage = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dropZone.ondrop = async function (event) {
|
dropZone.ondrop = async function (event) {
|
||||||
console.log("files dropped")
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -213,13 +203,9 @@ const changeFolder = async (newFolder) => {
|
|||||||
|
|
||||||
const createFolderData = ref({})
|
const createFolderData = ref({})
|
||||||
const createFolder = async () => {
|
const createFolder = async () => {
|
||||||
const {data,error} = await supabase
|
const res = await useEntities("folders").create({
|
||||||
.from("folders")
|
|
||||||
.insert({
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
parent: currentFolder.value ? currentFolder.value.id : undefined,
|
parent: currentFolder.value ? currentFolder.value.id : undefined,
|
||||||
name: createFolderData.value.name,
|
name: createFolderData.value.name,
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
createFolderModalOpen.value = false
|
createFolderModalOpen.value = false
|
||||||
@@ -229,61 +215,14 @@ const createFolder = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const downloadSelected = async () => {
|
const downloadSelected = async () => {
|
||||||
const bucket = "filesdev";
|
|
||||||
|
|
||||||
let files = []
|
let files = []
|
||||||
|
|
||||||
files = filteredDocuments.value.filter(i => selectedFiles.value[i.id] === true).map(i => i.path)
|
files = filteredDocuments.value.filter(i => selectedFiles.value[i.id] === true).map(i => i.path)
|
||||||
|
|
||||||
// If there are no files in the folder, throw an error
|
|
||||||
if (!files || !files.length) {
|
|
||||||
throw new Error("No files to download");
|
|
||||||
}
|
|
||||||
|
|
||||||
const promises = [];
|
await useFiles().downloadFile(undefined,Object.keys(selectedFiles.value))
|
||||||
|
|
||||||
// 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") {
|
|
||||||
|
|
||||||
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", "dateien.zip");
|
|
||||||
|
|
||||||
document.body.appendChild(link);
|
|
||||||
|
|
||||||
link.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchString = ref(tempStore.searchStrings["files"] ||'')
|
const searchString = ref(tempStore.searchStrings["files"] ||'')
|
||||||
@@ -295,7 +234,6 @@ const renderedFileList = computed(() => {
|
|||||||
type: "file"
|
type: "file"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(currentFolders.value)
|
|
||||||
|
|
||||||
arraySort(files, (a,b) => {
|
arraySort(files, (a,b) => {
|
||||||
let aVal = a.path ? a.path.split("/")[a.path.split("/").length -1] : null
|
let aVal = a.path ? a.path.split("/")[a.path.split("/").length -1] : null
|
||||||
@@ -338,7 +276,6 @@ const renderedFileList = computed(() => {
|
|||||||
const selectedFileIndex = ref(0)
|
const selectedFileIndex = ref(0)
|
||||||
|
|
||||||
const showFile = (fileId) => {
|
const showFile = (fileId) => {
|
||||||
console.log(fileId)
|
|
||||||
modal.open(DocumentDisplayModal,{
|
modal.open(DocumentDisplayModal,{
|
||||||
documentData: documents.value.find(i => i.id === fileId),
|
documentData: documents.value.find(i => i.id === fileId),
|
||||||
onUpdatedNeeded: setupPage()
|
onUpdatedNeeded: setupPage()
|
||||||
@@ -413,7 +350,10 @@ const clearSearchString = () => {
|
|||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
|
|
||||||
|
|
||||||
<UButton @click="modal.open(DocumentUploadModal,{fileData: {folder: currentFolder.id, type: currentFolder.standardFiletype, typeEnabled: currentFolder.standardFiletypeIsOptional}, onUploadFinished: () => {setupPage()}})">+ Datei</UButton>
|
<UButton
|
||||||
|
:disabled="!currentFolder"
|
||||||
|
@click="modal.open(DocumentUploadModal,{fileData: {folder: currentFolder.id, type: currentFolder.standardFiletype, typeEnabled: currentFolder.standardFiletypeIsOptional}, onUploadFinished: () => {setupPage()}})"
|
||||||
|
>+ Datei</UButton>
|
||||||
<UButton
|
<UButton
|
||||||
@click="createFolderModalOpen = true"
|
@click="createFolderModalOpen = true"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
const currentSubmission = (await supabase.from("formSubmits").select().eq('id',route.params.id)).data[0]
|
|
||||||
const form = (await supabase.from("forms").select().eq('id',currentSubmission.formType)).data[0]
|
|
||||||
|
|
||||||
const formData = ref({})
|
|
||||||
const submitted = ref(currentSubmission.submitted)
|
|
||||||
|
|
||||||
const submitForm = async () => {
|
|
||||||
|
|
||||||
submitted.value = true
|
|
||||||
|
|
||||||
console.log(formData.value)
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("formSubmits")
|
|
||||||
.update({values: formData.value, submitted: true})
|
|
||||||
.eq('id',currentSubmission.id)
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else if( data) {
|
|
||||||
formData.value = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<UForm
|
|
||||||
v-if="!submitted"
|
|
||||||
@submit="submitForm"
|
|
||||||
@reset="formData = {}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="item in form.fields"
|
|
||||||
>
|
|
||||||
<p v-if="item.type === 'header'">{{item.label}}</p>
|
|
||||||
<UFormGroup
|
|
||||||
v-else-if="item.type.includes('Input')"
|
|
||||||
:label="item.required ? item.label + '*' : item.label"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-if="item.type === 'textInput'"
|
|
||||||
v-model="formData[item.key]"
|
|
||||||
:required="item.required"
|
|
||||||
/>
|
|
||||||
<UInput
|
|
||||||
v-else-if="item.type === 'numberInput'"
|
|
||||||
v-model="formData[item.key]"
|
|
||||||
:required="item.required"
|
|
||||||
type="number"
|
|
||||||
inputmode="numeric"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UButton type="submit">
|
|
||||||
Abschicken
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
type="reset"
|
|
||||||
color="rose"
|
|
||||||
class="m-2"
|
|
||||||
>
|
|
||||||
Zurücksetzen
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
|
|
||||||
</UForm>
|
|
||||||
<div v-else>
|
|
||||||
Dieses Formular wurde bereits abgeschickt. Möchten Sie erneut Daten abschicken, sprechen Sie bitte Ihren Ansprechpartner an, um das Formular freizuschalten.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,676 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import InputGroup from "~/components/InputGroup.vue";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
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 costcentres = ref([])
|
|
||||||
const accounts = ref([])
|
|
||||||
const vendors = 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)
|
|
||||||
accounts.value = (await supabase.from("accounts").select().order("number",{ascending:true})).data
|
|
||||||
vendors.value = await useSupabaseSelect("vendors")
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
|
|
||||||
const loadCostCentres = async () => {
|
|
||||||
costcentres.value = await useSupabaseSelect("costcentres")
|
|
||||||
|
|
||||||
}
|
|
||||||
loadCostCentres()
|
|
||||||
|
|
||||||
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,true)
|
|
||||||
|
|
||||||
const {error} = await supabase.from("files").update({incominginvoice: data.id}).eq("id",loadedFile.value.id)
|
|
||||||
|
|
||||||
router.push(`/incominginvoices/show/${data.id}`)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCostCentre = async (item,data) => {
|
|
||||||
await loadCostCentres()
|
|
||||||
item.costCentre = data.id
|
|
||||||
}
|
|
||||||
|
|
||||||
const gptLoading = ref(false)
|
|
||||||
|
|
||||||
const getInvoiceData = async () => {
|
|
||||||
gptLoading.value = true
|
|
||||||
console.log(loadedFile.value)
|
|
||||||
|
|
||||||
|
|
||||||
//loadedFile.value.url
|
|
||||||
|
|
||||||
|
|
||||||
/*let data = {
|
|
||||||
"invoice_number": "3423478673",
|
|
||||||
"invoice_date": "2025-05-30",
|
|
||||||
"invoice_type": "incoming",
|
|
||||||
"delivery_type": "null",
|
|
||||||
"delivery_note_number": "null",
|
|
||||||
"reference": "null",
|
|
||||||
"issuer": {
|
|
||||||
"name": "Boels Rental Germany GmbH",
|
|
||||||
"address": "Emeranstraße 49-51, 85622 Feldkirchen, Deutschland",
|
|
||||||
"phone": "+49-(0)1801663225",
|
|
||||||
"email": "fakturierung@boels.de",
|
|
||||||
"bank": "ABN AMRO Bank N.V.",
|
|
||||||
"bic": "ABNANL2A",
|
|
||||||
"iban": "NL09 ABNA 0520 5585 61"
|
|
||||||
},
|
|
||||||
"recipient": {
|
|
||||||
"name": "Federspiel Technology UG",
|
|
||||||
"address": "Am Schwarzen Brack 14, 26452 Sande, Deutschland",
|
|
||||||
"phone": "null",
|
|
||||||
"email": "null"
|
|
||||||
},
|
|
||||||
"invoice_items": [
|
|
||||||
{
|
|
||||||
"description": "Bautrockner 50 ltr.",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 395.22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Servicepauschale Kat. A",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 32.1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Haftungsbegrenzung A: (Schäden, exkl. Feuer/Diebstahl/Einbruch)",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 3.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Haftungsbegrenzung B: (Feuer/Diebstahl/Einbruch)",
|
|
||||||
"unit": "piece",
|
|
||||||
"quantity": 1,
|
|
||||||
"total": 16.93
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subtotal": 89.1,
|
|
||||||
"tax_rate": 19,
|
|
||||||
"tax": 16.93,
|
|
||||||
"total": 106.03,
|
|
||||||
"terms": "Dieser Betrag wird automatisch mittels Lastschrift von ihrem Konto eingezogen"
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
console.log(data.subtotal)*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let data = await useFunctions().useGetInvoiceData(loadedFile.value)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(data.invoice_number) itemInfo.value.reference = data.invoice_number
|
|
||||||
if(data.invoice_date) itemInfo.value.date = dayjs(data.invoice_date)
|
|
||||||
if(data.issuer.id) itemInfo.value.vendor = data.issuer.id
|
|
||||||
if(data.invoice_duedate) itemInfo.value.dueDate = dayjs(data.invoice_duedate)
|
|
||||||
if(data.terms) itemInfo.value.paymentType = data.terms
|
|
||||||
if(data.subtotal) {
|
|
||||||
itemInfo.value.accounts = [
|
|
||||||
{
|
|
||||||
account: null,
|
|
||||||
amountNet: data.subtotal,
|
|
||||||
amountTax: data.tax,
|
|
||||||
taxType: String(data.tax_rate),
|
|
||||||
costCentre: null,
|
|
||||||
amountGross: Number(data.subtotal) + Number(data.tax)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
if(data.terms === "Direct Debit") {
|
|
||||||
itemInfo.value.paymentType = "Einzug"
|
|
||||||
} else if(data.terms === "Transfer") {
|
|
||||||
itemInfo.value.paymentType = "Überweisung"
|
|
||||||
} else if(data.terms === "Credit Card") {
|
|
||||||
itemInfo.value.paymentType = "Kreditkarte"
|
|
||||||
} else if(data.terms === "Other") {
|
|
||||||
itemInfo.value.paymentType = "Sonstiges"
|
|
||||||
}
|
|
||||||
|
|
||||||
let description = ""
|
|
||||||
|
|
||||||
if(data.delivery_note_number) description += `Lieferschein: ${data.delivery_note_number} \n`
|
|
||||||
if(data.reference) description += `Referenz: ${data.reference} \n`
|
|
||||||
if(data.invoice_items) {
|
|
||||||
data.invoice_items.forEach(item => {
|
|
||||||
description += `${item.description} - ${item.quantity} ${item.unit} - ${item.total}\n`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
itemInfo.value.description = description
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
gptLoading.value = false
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
@click="createIncomingInvoice"
|
|
||||||
>
|
|
||||||
Speichern
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardPanelContent>
|
|
||||||
<div v-if="!loadedFile">
|
|
||||||
<DocumentList
|
|
||||||
v-if="availableDocuments.length > 0"
|
|
||||||
:documents="availableDocuments"
|
|
||||||
:return-document-id="true"
|
|
||||||
@selectDocument="(documentId) => loadFile(documentId)"
|
|
||||||
/>
|
|
||||||
<div v-else class="w-full text-center">
|
|
||||||
<span class="text-xl font-medium mt-10">Keine Dateien zum zuweisen verfügbar</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="flex justify-between mt-5 workingContainer"
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
<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">
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-sparkles"
|
|
||||||
class="my-3"
|
|
||||||
variant="outline"
|
|
||||||
@click="getInvoiceData"
|
|
||||||
:disabled="gptLoading"
|
|
||||||
>
|
|
||||||
KI - Vorschlag
|
|
||||||
<UProgress v-if="gptLoading" animation="carousel"/>
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
<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:" >
|
|
||||||
<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..."
|
|
||||||
:color="!itemInfo.vendor ? 'rose' : 'primary'"
|
|
||||||
@change="vendors.find(i => i.id === itemInfo.vendor).defaultPaymentMethod ? itemInfo.paymentType = vendors.find(i => i.id === itemInfo.vendor).defaultPaymentMethod : null"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
<EntityModalButtons
|
|
||||||
type="vendors"
|
|
||||||
:id="itemInfo.vendor"
|
|
||||||
@return-data="(data) => itemInfo.vendor = data.id"
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
class="mt-3"
|
|
||||||
label="Rechnungsreferenz:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="itemInfo.reference"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<InputGroup class="mt-3" gap="2">
|
|
||||||
<UFormGroup label="Rechnungsdatum:">
|
|
||||||
<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'"
|
|
||||||
variant="outline"
|
|
||||||
:color="!itemInfo.date ? 'rose' : 'primary'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #panel="{ close }">
|
|
||||||
<LazyDatePicker v-model="itemInfo.date" @close="itemInfo.dueDate = itemInfo.date" />
|
|
||||||
</template>
|
|
||||||
</UPopover>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup label="Fälligkeitsdatum:">
|
|
||||||
<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'"
|
|
||||||
variant="outline"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #panel="{ close }">
|
|
||||||
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
|
|
||||||
</template>
|
|
||||||
</UPopover>
|
|
||||||
</UFormGroup>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<UFormGroup label="Zahlart:" >
|
|
||||||
<USelectMenu
|
|
||||||
:options="['Einzug','Kreditkarte','Überweisung','Sonstiges']"
|
|
||||||
v-model="itemInfo.paymentType"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup label="Beschreibung:" >
|
|
||||||
<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="accounts"
|
|
||||||
option-attribute="label"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['label']"
|
|
||||||
searchable-placeholder="Suche..."
|
|
||||||
v-model="item.account"
|
|
||||||
:color="!item.account ? 'rose' : 'primary'"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{accounts.find(account => account.id === item.account) ? accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}
|
|
||||||
</template>
|
|
||||||
<template #option="{option}">
|
|
||||||
{{option.number}} - {{option.label}}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Kostenstelle"
|
|
||||||
class=" mb-3"
|
|
||||||
>
|
|
||||||
<InputGroup class="w-full">
|
|
||||||
<USelectMenu
|
|
||||||
:options="costcentres"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['label']"
|
|
||||||
searchable-placeholder="Suche..."
|
|
||||||
v-model="item.costCentre"
|
|
||||||
class="flex-auto"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{costcentres.find(i => i.id === item.costCentre) ? costcentres.find(i => i.id === item.costCentre).name : "Keine Kostenstelle ausgewählt" }}
|
|
||||||
</template>
|
|
||||||
<template #option="{option}">
|
|
||||||
<span v-if="option.vehicle">{{option.number}} - Fahrzeug - {{option.name}}</span>
|
|
||||||
<span v-else-if="option.project">{{option.number}} - Projekt - {{option.name}}</span>
|
|
||||||
<span v-else-if="option.inventoryitem">{{option.number}} - Inventarartikel - {{option.name}}</span>
|
|
||||||
<span v-else>{{option.number}} - {{option.name}}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</USelectMenu>
|
|
||||||
<UButton
|
|
||||||
variant="outline"
|
|
||||||
color="rose"
|
|
||||||
v-if="item.costCentre"
|
|
||||||
icon="i-heroicons-x-mark"
|
|
||||||
@click="item.costCentre = null"
|
|
||||||
/>
|
|
||||||
<EntityModalButtons
|
|
||||||
type="costcentres"
|
|
||||||
:id="item.costCentre"
|
|
||||||
@return-data="(data) => setCostCentre(item,data)"
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<InputGroup>
|
|
||||||
<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"
|
|
||||||
:color="!item.amountNet ? 'rose' : 'primary'"
|
|
||||||
:disabled="item.taxType === null"
|
|
||||||
@keyup="item.amountTax = Number((item.amountNet * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
|
||||||
item.amountGross = Number(item.amountNet) + Number(item.amountTax)"
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
:color="!item.amountGross ? 'rose' : 'primary'"
|
|
||||||
:ui-menu="{ width: 'min-w-max' }"
|
|
||||||
@keyup="item.amountNet = Number((item.amountGross / (1 + Number(taxOptions.find(i => i.key === item.taxType).percentage)/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>
|
|
||||||
<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"
|
|
||||||
:ui-menu="{ width: 'min-w-max' }"
|
|
||||||
option-attribute="label"
|
|
||||||
@change="item.amountNet = Number((item.amountGross / (1 + Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
|
||||||
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>
|
|
||||||
</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)]"
|
|
||||||
>
|
|
||||||
Betrag aufteilen
|
|
||||||
</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;
|
|
||||||
height: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workingContainer {
|
|
||||||
height: 80vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -2,18 +2,11 @@
|
|||||||
import InputGroup from "~/components/InputGroup.vue";
|
import InputGroup from "~/components/InputGroup.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const itemInfo = ref({
|
const itemInfo = ref({
|
||||||
vendor: 0,
|
vendor: 0,
|
||||||
@@ -36,14 +29,18 @@ const itemInfo = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const costcentres = ref([])
|
const costcentres = ref([])
|
||||||
|
const vendors = ref([])
|
||||||
|
const accounts = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
let filetype = (await supabase.from("filetags").select().eq("tenant",profileStore.currentTenant).eq("incomingDocumentType","invoices").single()).data.id
|
let filetype = (await useEntities("filetags").select()).find(i=> i.incomingDocumentType === "invoices").id
|
||||||
console.log(filetype)
|
console.log(filetype)
|
||||||
|
|
||||||
costcentres.value = await useSupabaseSelect("costcentres")
|
costcentres.value = await useEntities("costcentres").select()
|
||||||
|
vendors.value = await useEntities("vendors").select()
|
||||||
|
accounts.value = await useEntities("accounts").selectSpecial()
|
||||||
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("incominginvoices", route.params.id, "*, files(*)")
|
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id, "*, files(*)")
|
||||||
await loadFile(itemInfo.value.files[itemInfo.value.files.length-1].id)
|
await loadFile(itemInfo.value.files[itemInfo.value.files.length-1].id)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -65,7 +62,7 @@ const loadFile = async (id) => {
|
|||||||
const changeNetMode = (mode) => {
|
const changeNetMode = (mode) => {
|
||||||
useNetMode.value = mode
|
useNetMode.value = mode
|
||||||
|
|
||||||
itemInfo.value.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]
|
//itemInfo.value.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -139,7 +136,7 @@ const updateIncomingInvoice = async (setBooked = false) => {
|
|||||||
} else {
|
} else {
|
||||||
item.state = "Entwurf"
|
item.state = "Entwurf"
|
||||||
}
|
}
|
||||||
const data = await dataStore.updateItem('incominginvoices',item)
|
const data = await useEntities('incominginvoices').update(itemInfo.value.id,item)
|
||||||
}
|
}
|
||||||
|
|
||||||
const findIncomingInvoiceErrors = computed(() => {
|
const findIncomingInvoiceErrors = computed(() => {
|
||||||
@@ -172,6 +169,12 @@ const findIncomingInvoiceErrors = computed(() => {
|
|||||||
<template>
|
<template>
|
||||||
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
||||||
<template #right>
|
<template #right>
|
||||||
|
<ArchiveButton
|
||||||
|
color="rose"
|
||||||
|
variant="outline"
|
||||||
|
type="incominginvoices"
|
||||||
|
@confirmed="useEntities('incominginvoices').archive(route.params.id)"
|
||||||
|
/>
|
||||||
<UButton
|
<UButton
|
||||||
@click="updateIncomingInvoice(false)"
|
@click="updateIncomingInvoice(false)"
|
||||||
>
|
>
|
||||||
@@ -234,7 +237,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
|||||||
<InputGroup>
|
<InputGroup>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="itemInfo.vendor"
|
v-model="itemInfo.vendor"
|
||||||
:options="dataStore.vendors"
|
:options="vendors"
|
||||||
option-attribute="name"
|
option-attribute="name"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
searchable
|
searchable
|
||||||
@@ -247,7 +250,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
|||||||
{{option.vendorNumber}} - {{option.name}}
|
{{option.vendorNumber}} - {{option.name}}
|
||||||
</template>
|
</template>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor) ? dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor).name : 'Lieferant auswählen'}}
|
{{vendors.find(vendor => vendor.id === itemInfo.vendor) ? vendors.find(vendor => vendor.id === itemInfo.vendor).name : 'Lieferant auswählen'}}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<EntityModalButtons
|
<EntityModalButtons
|
||||||
@@ -369,7 +372,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
|||||||
class="mb-3"
|
class="mb-3"
|
||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="dataStore.accounts"
|
:options="accounts"
|
||||||
option-attribute="label"
|
option-attribute="label"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
searchable
|
searchable
|
||||||
@@ -379,7 +382,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
|||||||
:color="!item.account ? 'rose' : 'primary'"
|
:color="!item.account ? 'rose' : 'primary'"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.accounts.find(account => account.id === item.account) ? dataStore.accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}
|
{{accounts.find(account => account.id === item.account) ? accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import {useSum} from "~/composables/useSum.js";
|
import {useSum} from "~/composables/useSum.js";
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'/': () => {
|
'/': () => {
|
||||||
@@ -44,64 +42,53 @@ const sum = useSum()
|
|||||||
|
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const selectedItem = ref(0)
|
const selectedItem = ref(0)
|
||||||
|
const sort = ref({
|
||||||
|
column: 'date',
|
||||||
|
direction: 'desc'
|
||||||
|
})
|
||||||
|
|
||||||
|
const type = "incominginvoices"
|
||||||
|
const dataType = dataStore.dataTypes[type]
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
items.value = await useSupabaseSelect("incominginvoices","*, vendor(id,name), statementallocations(id,amount)","created_at",false)
|
items.value = await useEntities(type).select("*, vendor(id,name), statementallocations(id,amount)",sort.value.column,sort.value.direction === "asc")
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
|
|
||||||
const templateColumns = [
|
const selectedColumns = ref(tempStore.columns[type] ? tempStore.columns[type] : dataType.templateColumns.filter(i => !i.disabledInTable))
|
||||||
{
|
const columns = computed(() => dataType.templateColumns.filter((column) => !column.disabledInTable && selectedColumns.value.find(i => i.key === column.key)))
|
||||||
key: 'reference',
|
|
||||||
label: "Referenz:"
|
|
||||||
}, {
|
|
||||||
key: 'state',
|
|
||||||
label: "Status:"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "date",
|
|
||||||
label: "Datum"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "vendor",
|
|
||||||
label: "Lieferant"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "amount",
|
|
||||||
label: "Betrag"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "dueDate",
|
|
||||||
label: "Fälligkeitsdatum"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "paid",
|
|
||||||
label: "Bezahlt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "paymentType",
|
|
||||||
label: "Zahlart"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "description",
|
|
||||||
label: "Beschreibung"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const selectedColumns = ref(templateColumns)
|
|
||||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
|
||||||
|
|
||||||
|
const selectableFilters = ref(dataType.filters.map(i => i.name))
|
||||||
|
const selectedFilters = ref(dataType.filters.filter(i => i.default).map(i => i.name) || [])
|
||||||
|
|
||||||
const searchString = ref(tempStore.searchStrings['incominginvoices'] ||'')
|
const searchString = ref(tempStore.searchStrings[type] ||'')
|
||||||
|
|
||||||
const clearSearchString = () => {
|
const clearSearchString = () => {
|
||||||
tempStore.clearSearchString('incominginvoices')
|
tempStore.clearSearchString(type)
|
||||||
searchString.value = ''
|
searchString.value = ''
|
||||||
}
|
}
|
||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
let filteredItems = useSearch(searchString.value, items.value)
|
let tempItems = items.value.map(i => {
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
class: i.archived ? 'bg-red-500/50 dark:bg-red-400/50' : null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return [...filteredItems.filter(i => i.state === "Vorbereitet"), ...filteredItems.filter(i => i.state !== "Vorbereitet")]
|
if(selectedFilters.value.length > 0) {
|
||||||
|
selectedFilters.value.forEach(filterName => {
|
||||||
|
let filter = dataType.filters.find(i => i.name === filterName)
|
||||||
|
tempItems = tempItems.filter(filter.filterFunction)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tempItems = useSearch(searchString.value, tempItems)
|
||||||
|
|
||||||
|
|
||||||
|
return [...tempItems.filter(i => i.state === "Vorbereitet"), ...tempItems.filter(i => i.state !== "Vorbereitet")]
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -149,7 +136,7 @@ const selectIncomingInvoice = (invoice) => {
|
|||||||
placeholder="Suche..."
|
placeholder="Suche..."
|
||||||
class="hidden lg:block"
|
class="hidden lg:block"
|
||||||
@keydown.esc="$event.target.blur()"
|
@keydown.esc="$event.target.blur()"
|
||||||
@change="tempStore.modifySearchString('incominginvoices',searchString)"
|
@change="tempStore.modifySearchString(type,searchString)"
|
||||||
>
|
>
|
||||||
<template #trailing>
|
<template #trailing>
|
||||||
<UKbd value="/" />
|
<UKbd value="/" />
|
||||||
@@ -171,21 +158,54 @@ const selectIncomingInvoice = (invoice) => {
|
|||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="selectedColumns"
|
v-model="selectedColumns"
|
||||||
icon="i-heroicons-adjustments-horizontal-solid"
|
icon="i-heroicons-adjustments-horizontal-solid"
|
||||||
:options="templateColumns"
|
:options="dataType.templateColumns.filter(i => !i.disabledInTable)"
|
||||||
multiple
|
multiple
|
||||||
class="hidden lg:block"
|
class="hidden lg:block"
|
||||||
by="key"
|
by="key"
|
||||||
|
:color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'"
|
||||||
|
:ui-menu="{ width: 'min-w-max' }"
|
||||||
|
@change="tempStore.modifyColumns(type,selectedColumns)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
Spalten
|
Spalten
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</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
|
||||||
|
</template>
|
||||||
|
</USelectMenu>
|
||||||
</template>
|
</template>
|
||||||
</UDashboardToolbar>
|
</UDashboardToolbar>
|
||||||
|
|
||||||
<UDashboardPanelContent>
|
<UTabs
|
||||||
|
class="m-3"
|
||||||
|
:items="[{label: 'In Bearbeitung'},{label: 'Gebucht'}]"
|
||||||
|
>
|
||||||
|
<template #default="{item}">
|
||||||
|
{{item.label}}
|
||||||
|
<UBadge
|
||||||
|
variant="outline"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
{{filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' ).length}}
|
||||||
|
</UBadge>
|
||||||
|
</template>
|
||||||
|
<template #item="{item}">
|
||||||
|
<div style="height: 80dvh; overflow-y: scroll">
|
||||||
<UTable
|
<UTable
|
||||||
:rows="filteredRows"
|
v-model:sort="sort"
|
||||||
|
sort-mode="manual"
|
||||||
|
@update:sort="setupPage"
|
||||||
|
:rows="filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' )"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||||
@@ -218,7 +238,10 @@ const selectIncomingInvoice = (invoice) => {
|
|||||||
<span v-else class="text-rose-600">Offen</span>
|
<span v-else class="text-rose-600">Offen</span>
|
||||||
</template>
|
</template>
|
||||||
</UTable>
|
</UTable>
|
||||||
</UDashboardPanelContent>
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</UTabs>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -45,7 +42,7 @@ const loading = ref(true)
|
|||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
if((mode.value === "show") && route.params.id){
|
if((mode.value === "show") && route.params.id){
|
||||||
itemInfo.value = await useSupabaseSelectSingle("incominginvoices",route.params.id,"*, files(*), vendor(*)")
|
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id,"*, files(*), vendor(*)")
|
||||||
if(process.dev) console.log(itemInfo.value)
|
if(process.dev) console.log(itemInfo.value)
|
||||||
currentDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
currentDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<UDashboardNavbar title="Home">
|
<UDashboardNavbar title="Home">
|
||||||
<template #right>
|
<template #right>
|
||||||
<UTooltip text="Notifications" :shortcuts="['N']">
|
<!-- <UTooltip text="Notifications" :shortcuts="['N']">
|
||||||
<UButton color="gray" variant="ghost" square @click="isNotificationsSlideoverOpen = true">
|
<UButton color="gray" variant="ghost" square @click="isNotificationsSlideoverOpen = true">
|
||||||
<UChip :show="unreadMessages" color="primary" inset>
|
<UChip :show="unreadMessages" color="primary" inset>
|
||||||
<UIcon name="i-heroicons-bell" class="w-5 h-5" />
|
<UIcon name="i-heroicons-bell" class="w-5 h-5" />
|
||||||
</UChip>
|
</UChip>
|
||||||
</UButton>
|
</UButton>
|
||||||
</UTooltip>
|
</UTooltip>-->
|
||||||
</template>
|
</template>
|
||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
|
|
||||||
@@ -23,13 +23,11 @@
|
|||||||
<UPageGrid>
|
<UPageGrid>
|
||||||
<UDashboardCard
|
<UDashboardCard
|
||||||
title="Buchhaltung"
|
title="Buchhaltung"
|
||||||
v-if="profileStore.ownTenant.features.accounting"
|
|
||||||
>
|
>
|
||||||
<display-open-balances/>
|
<display-open-balances/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>
|
||||||
<UDashboardCard
|
<UDashboardCard
|
||||||
title="Bank"
|
title="Bank"
|
||||||
v-if="profileStore.ownTenant.features.accounting"
|
|
||||||
>
|
>
|
||||||
<display-bankaccounts/>
|
<display-bankaccounts/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>
|
||||||
@@ -38,7 +36,7 @@
|
|||||||
>
|
>
|
||||||
<display-projects-in-phases/>
|
<display-projects-in-phases/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>
|
||||||
<UDashboardCard
|
<!--<UDashboardCard
|
||||||
title="Anwesende"
|
title="Anwesende"
|
||||||
>
|
>
|
||||||
<display-present-profiles/>
|
<display-present-profiles/>
|
||||||
@@ -52,7 +50,7 @@
|
|||||||
title="Anwesenheiten"
|
title="Anwesenheiten"
|
||||||
>
|
>
|
||||||
<display-running-working-time/>
|
<display-running-working-time/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>-->
|
||||||
<UDashboardCard
|
<UDashboardCard
|
||||||
title="Aufgaben"
|
title="Aufgaben"
|
||||||
>
|
>
|
||||||
@@ -64,36 +62,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth","redirect-to-mobile-index"]
|
middleware: 'redirect-to-mobile-index'
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const toast = useToast()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const { isNotificationsSlideoverOpen } = useDashboard()
|
const { isNotificationsSlideoverOpen } = useDashboard()
|
||||||
const items = [[{
|
|
||||||
label: 'Aufgabe',
|
|
||||||
icon: 'i-heroicons-paper-airplane',
|
|
||||||
to: '/tasks/create'
|
|
||||||
}, {
|
|
||||||
label: 'Kunde',
|
|
||||||
icon: 'i-heroicons-user-plus',
|
|
||||||
to: '/customers/create'
|
|
||||||
}]]
|
|
||||||
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const user = useSupabaseUser()
|
|
||||||
|
|
||||||
const unreadMessages = ref(false)
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
unreadMessages.value = (await supabase.from("notifications").select("id,read").eq("read",false)).data.length > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|||||||
@@ -1,428 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
|
||||||
const mode = ref("incoming")
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const inventoryChangeData = ref({
|
|
||||||
productId: null,
|
|
||||||
sourceSpaceId: null,
|
|
||||||
sourceProjectId: null,
|
|
||||||
destinationSpaceId: null,
|
|
||||||
destinationProjectId: null,
|
|
||||||
quantity: 1,
|
|
||||||
serials: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const resetInput = () => {
|
|
||||||
inventoryChangeData.value = {
|
|
||||||
productId: null,
|
|
||||||
sourceSpaceId: null,
|
|
||||||
sourceProjectId: null,
|
|
||||||
destinationSpaceId: null,
|
|
||||||
destinationProjectId: null,
|
|
||||||
quantity: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const createMovement = async () => {
|
|
||||||
|
|
||||||
let movements = []
|
|
||||||
|
|
||||||
if(mode.value === 'incoming'){
|
|
||||||
|
|
||||||
let movement = {
|
|
||||||
productId: inventoryChangeData.value.productId,
|
|
||||||
spaceId: inventoryChangeData.value.destinationSpaceId,
|
|
||||||
projectId: inventoryChangeData.value.destinationProjectId,
|
|
||||||
quantity: inventoryChangeData.value.quantity,
|
|
||||||
profileId: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
|
|
||||||
movements.push(movement)
|
|
||||||
|
|
||||||
/*const {error} = await supabase
|
|
||||||
.from("movements")
|
|
||||||
.insert([inventoryChangeData.value])
|
|
||||||
.select()
|
|
||||||
if(error) console.log(error)*/
|
|
||||||
|
|
||||||
|
|
||||||
} else if (mode.value === 'outgoing'){
|
|
||||||
|
|
||||||
let movement = {
|
|
||||||
productId: inventoryChangeData.value.productId,
|
|
||||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
|
||||||
projectId: inventoryChangeData.value.sourceProjectId,
|
|
||||||
quantity: inventoryChangeData.value.quantity * -1,
|
|
||||||
profileId: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
|
|
||||||
movements.push(movement)
|
|
||||||
} else if (mode.value === 'change'){
|
|
||||||
let outMovement = {
|
|
||||||
productId: inventoryChangeData.value.productId,
|
|
||||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
|
||||||
projectId: inventoryChangeData.value.sourceProjectId,
|
|
||||||
quantity: inventoryChangeData.value.quantity * -1,
|
|
||||||
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: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
|
|
||||||
movements.push(outMovement)
|
|
||||||
movements.push(inMovement)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(movements)
|
|
||||||
|
|
||||||
const {error} = await supabase
|
|
||||||
.from("movements")
|
|
||||||
.insert(movements)
|
|
||||||
.select()
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
resetInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
meta_enter: {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
createMovement()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function checkProductId(productId) {
|
|
||||||
return dataStore.products.filter(product =>product.id === productId).length > 0;
|
|
||||||
}
|
|
||||||
function checkSpaceId(spaceId) {
|
|
||||||
return dataStore.spaces.filter(space => space.id === spaceId).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkProjectId(projectId) {
|
|
||||||
return dataStore.projects.some(i => i.id === projectId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeFocusToSpaceId() {
|
|
||||||
document.getElementById('spaceIdInput').focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeFocusToQuantity() {
|
|
||||||
document.getElementById('quantityInput').focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeFocusToBarcode() {
|
|
||||||
document.getElementById('barcodeInput').focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const findProductByBarcodeOrEAN = (input) => {
|
|
||||||
return dataStore.products.find(i => i.barcode === input || i.ean === input || i.articleNumber === input)
|
|
||||||
}
|
|
||||||
|
|
||||||
const findSpaceBySpaceNumber = (input) => {
|
|
||||||
return dataStore.spaces.find(i => i.spaceNumber === input)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const barcodeInput = ref("")
|
|
||||||
const showBarcodeTip = ref(true)
|
|
||||||
const serialInput = ref("")
|
|
||||||
|
|
||||||
const processBarcodeInput = () => {
|
|
||||||
if(findProductByBarcodeOrEAN(barcodeInput.value) && !findSpaceBySpaceNumber(barcodeInput.value)){
|
|
||||||
//Set Product
|
|
||||||
|
|
||||||
inventoryChangeData.value.productId = findProductByBarcodeOrEAN(barcodeInput.value).id
|
|
||||||
} else if (!findProductByBarcodeOrEAN(barcodeInput.value) && findSpaceBySpaceNumber(barcodeInput.value)){
|
|
||||||
//Set Space
|
|
||||||
|
|
||||||
if(mode.value === 'incoming'){
|
|
||||||
inventoryChangeData.value.destinationSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
} else if(mode.value === 'outgoing') {
|
|
||||||
inventoryChangeData.value.sourceSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
} else if(mode.value === 'change') {
|
|
||||||
if(!inventoryChangeData.value.sourceSpaceId){
|
|
||||||
inventoryChangeData.value.sourceSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
} else {
|
|
||||||
inventoryChangeData.value.destinationSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//console.log(findSpaceBySpaceNumber(barcodeInput.value))
|
|
||||||
}
|
|
||||||
barcodeInput.value = ""
|
|
||||||
//console.log(movementData.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar
|
|
||||||
title="Lager Vorgänge"
|
|
||||||
>
|
|
||||||
<template #right>
|
|
||||||
<UButton
|
|
||||||
@click="resetInput"
|
|
||||||
class="mt-3"
|
|
||||||
color="rose"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
Abbrechen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="createMovement"
|
|
||||||
:disabled="mode === '' && checkSpaceId(inventoryChangeData.spaceId) && checkProductId(inventoryChangeData.productId)"
|
|
||||||
class="mt-3"
|
|
||||||
>
|
|
||||||
Bestätigen
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardPanelContent>
|
|
||||||
<div class="w-80 mx-auto mt-5">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<UButton
|
|
||||||
@click="mode = 'incoming'"
|
|
||||||
class="my-2"
|
|
||||||
:variant="mode === 'incoming' ? 'solid' : 'outline'"
|
|
||||||
>Wareneingang</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="mode = 'outgoing'"
|
|
||||||
class="my-2"
|
|
||||||
:variant="mode === 'outgoing' ? 'solid' : 'outline'"
|
|
||||||
>Warenausgang</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="mode = 'change'"
|
|
||||||
class="my-2"
|
|
||||||
:variant="mode === 'change' ? 'solid' : 'outline'"
|
|
||||||
>Umlagern</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UAlert
|
|
||||||
title="Info"
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
v-if="showBarcodeTip"
|
|
||||||
@close="showBarcodeTip = false"
|
|
||||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }"
|
|
||||||
description="Über die Barcode Eingabe können folgende Werte automatisch erkannt werden: Quelllagerplatz, Ziellagerplatz, Artikel (EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <UTooltip
|
|
||||||
text="Über die Barcode Eingabe könenn folgende Werte automatisch erkannt werden: Quell Lagerplatz, Ziellagerplatz, Artikel(EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt."
|
|
||||||
>-->
|
|
||||||
<UFormGroup
|
|
||||||
label="Barcode:"
|
|
||||||
class="mt-3"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
@keyup.enter="processBarcodeInput"
|
|
||||||
@focusout="processBarcodeInput"
|
|
||||||
@input="processBarcodeInput"
|
|
||||||
v-model="barcodeInput"
|
|
||||||
id="barcodeInput"
|
|
||||||
|
|
||||||
/>
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
<!-- <template #text>
|
|
||||||
<span class="text-wrap">Über die Barcode Eingabe könenn folgende Werte automatisch erkannt werden: Quell Lagerplatz, Ziellagerplatz, Artikel(EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt.</span>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UTooltip>-->
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
v-if="mode !== 'incoming'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Quell Lagerplatz:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'incoming' "
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.spaces"
|
|
||||||
searchable
|
|
||||||
option-attribute="spaceNumber"
|
|
||||||
:color="checkSpaceId(inventoryChangeData.sourceSpaceId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.sourceSpaceId"
|
|
||||||
@change="inventoryChangeData.sourceProjectId = null"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.spaces.find(space => space.id === inventoryChangeData.sourceSpaceId) ? dataStore.spaces.find(space => space.id === inventoryChangeData.sourceSpaceId).description : "Kein Lagerplatz ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Quell Projekt:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'incoming' "
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.projects"
|
|
||||||
searchable
|
|
||||||
option-attribute="name"
|
|
||||||
:color="checkProjectId(inventoryChangeData.sourceProjectId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.sourceProjectId"
|
|
||||||
@change="inventoryChangeData.sourceSpaceId = null"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.getProjectById(inventoryChangeData.sourceProjectId) ? dataStore.getProjectById(inventoryChangeData.sourceProjectId).name : "Kein Projekt ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Artikel:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.products"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
variant="outline"
|
|
||||||
searchable
|
|
||||||
:search-attributes="['name','ean', 'barcode']"
|
|
||||||
:color="checkProductId(inventoryChangeData.productId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.productId"
|
|
||||||
v-on:select="changeFocusToSpaceId"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.products.find(product => product.id === inventoryChangeData.productId) ? dataStore.products.find(product => product.id === inventoryChangeData.productId).name : "Bitte Artikel auswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
v-if="mode !== 'outgoing'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Ziel Lagerplatz:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'outgoing'"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.spaces"
|
|
||||||
searchable
|
|
||||||
option-attribute="spaceNumber"
|
|
||||||
:color="checkSpaceId(inventoryChangeData.destinationSpaceId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.destinationSpaceId"
|
|
||||||
@change="inventoryChangeData.destinationProjectId = null"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.spaces.find(space => space.id === inventoryChangeData.destinationSpaceId) ? dataStore.spaces.find(space => space.id === inventoryChangeData.destinationSpaceId).description : "Kein Lagerplatz ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Ziel Projekt:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
v-if="mode !== 'outgoing'"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.projects"
|
|
||||||
searchable
|
|
||||||
option-attribute="name"
|
|
||||||
:color="checkProjectId(inventoryChangeData.destinationProjectId) ? 'primary' : 'rose'"
|
|
||||||
v-model="inventoryChangeData.destinationProjectId"
|
|
||||||
value-attribute="id"
|
|
||||||
@change="inventoryChangeData.destinationSpaceId = null"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.getProjectById(inventoryChangeData.destinationProjectId) ? dataStore.getProjectById(inventoryChangeData.destinationProjectId).name : "Kein Projekt ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UDivider
|
|
||||||
class="mt-5 w-80"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Anzahl:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
placeholder="Anzahl"
|
|
||||||
v-model="inventoryChangeData.quantity"
|
|
||||||
type="number"
|
|
||||||
id="quantityInput"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Seriennummern:"
|
|
||||||
class="mt-3 w-80"
|
|
||||||
>
|
|
||||||
<InputGroup class="w-full">
|
|
||||||
<UInput
|
|
||||||
variant="outline"
|
|
||||||
color="primary"
|
|
||||||
placeholder="Seriennummern"
|
|
||||||
v-model="serialInput"
|
|
||||||
/>
|
|
||||||
<UButton
|
|
||||||
@click="inventoryChangeData.serials.push(serialInput)"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</UButton>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li v-for="serial in inventoryChangeData.serials">{{serial}}</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</UDashboardPanelContent>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
<template>
|
|
||||||
<UDashboardNavbar title="Bestände" :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(`/products/create`)">+ Artikel</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(`/products/show/${i.id}`) "
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel 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 #stock-data="{row}">
|
|
||||||
{{`${dataStore.getStockByProductId(row.id)} ${(dataStore.units.find(unit => unit.id === row.unit) ? dataStore.units.find(unit => unit.id === row.unit).name : "")}`}}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
</UTable>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'/': () => {
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},
|
|
||||||
'+': () => {
|
|
||||||
router.push("/products/create")
|
|
||||||
},
|
|
||||||
'Enter': {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
router.push(`/products/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("products","*")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
|
|
||||||
const templateColumns = [
|
|
||||||
{
|
|
||||||
key: "stock",
|
|
||||||
label: "Bestand"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "Name",
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const selectedColumns = ref(templateColumns)
|
|
||||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
|
||||||
|
|
||||||
const templateTags = computed(() => {
|
|
||||||
let temp = []
|
|
||||||
|
|
||||||
dataStore.products.forEach(row => {
|
|
||||||
row.tags.forEach(tag => {
|
|
||||||
if(!temp.includes(tag)) temp.push(tag)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return temp
|
|
||||||
|
|
||||||
})
|
|
||||||
const selectedTags = ref(templateTags.value)
|
|
||||||
|
|
||||||
const searchString = ref('')
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
let temp = items.value.filter(i => i.tags.some(x => selectedTags.value.includes(x)) || i.tags.length === 0)
|
|
||||||
|
|
||||||
return useSearch(searchString.value, temp)
|
|
||||||
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
139
pages/login.vue
139
pages/login.vue
@@ -1,113 +1,31 @@
|
|||||||
<script setup >
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {useProfileStore} from "~/stores/profile.js";
|
|
||||||
import {useCapacitor} from "~/composables/useCapacitor.js";
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "notLoggedIn"
|
layout: "notLoggedIn"
|
||||||
})
|
})
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
const auth = useAuthStore()
|
||||||
const user = useSupabaseUser()
|
|
||||||
const router = useRouter()
|
|
||||||
const colorMode = useColorMode()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const isLight = computed({
|
|
||||||
get () {
|
|
||||||
return colorMode.value !== 'dark'
|
|
||||||
},
|
|
||||||
set () {
|
|
||||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const email = ref("")
|
|
||||||
const password = ref("")
|
|
||||||
|
|
||||||
const fields = [{
|
|
||||||
name: 'email',
|
|
||||||
type: 'text',
|
|
||||||
label: 'Email',
|
|
||||||
placeholder: 'E-Mail Adresse'
|
|
||||||
}, {
|
|
||||||
name: 'password',
|
|
||||||
label: 'Password',
|
|
||||||
type: 'password',
|
|
||||||
placeholder: 'Passwort'
|
|
||||||
}]
|
|
||||||
|
|
||||||
const authenticateWithAzure = async () => {
|
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
||||||
provider: 'azure',
|
|
||||||
options: {
|
|
||||||
scopes: 'email',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
|
||||||
|
|
||||||
const {error, data:{ user}} = await supabase.auth.signInWithPassword({
|
|
||||||
email: data.email,
|
|
||||||
password: data.password
|
|
||||||
})
|
|
||||||
if(error) {
|
|
||||||
if(error.toString().toLowerCase().includes("invalid")){
|
|
||||||
toast.add({title:"Zugangsdaten falsch",color:"rose"})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const doLogin = async (data:any) => {
|
||||||
|
try {
|
||||||
|
await auth.login(data.email, data.password)
|
||||||
|
// Weiterleiten nach erfolgreichem Login
|
||||||
|
toast.add({title:"Einloggen erfolgreich"})
|
||||||
|
if(useCapacitor().getIsNative()) {
|
||||||
|
return navigateTo("/mobile")
|
||||||
} else {
|
} else {
|
||||||
//console.log("Login Successful")
|
return navigateTo("/")
|
||||||
profileStore.initializeData(user.id)
|
|
||||||
|
|
||||||
if(await useCapacitor().getIsPhone()) {
|
|
||||||
router.push("/mobile")
|
|
||||||
} else {
|
|
||||||
router.push("/")
|
|
||||||
}
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.add({title:"Zugangsdaten falsch. Bitte überprüfen Sie Ihre Eingaben",color:"rose"})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- <div id="loginSite">
|
|
||||||
<div id="loginForm">
|
|
||||||
<UFormGroup
|
|
||||||
label="E-Mail:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="email"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Passwort:"
|
|
||||||
>
|
|
||||||
<UInput
|
|
||||||
v-model="password"
|
|
||||||
type="password"
|
|
||||||
@keyup.enter="onSubmit"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
<UButton
|
|
||||||
@click="onSubmit"
|
|
||||||
class="mt-3"
|
|
||||||
>
|
|
||||||
Einloggen
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>-->
|
|
||||||
<UCard class="max-w-sm w-full mx-auto mt-5">
|
<UCard class="max-w-sm w-full mx-auto mt-5">
|
||||||
|
|
||||||
<UColorModeImage
|
<UColorModeImage
|
||||||
@@ -115,6 +33,15 @@ const onSubmit = async (data) => {
|
|||||||
dark="/Logo_Dark.png"
|
dark="/Logo_Dark.png"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<UAlert
|
||||||
|
title="Achtung"
|
||||||
|
description="Es wurden alle Benutzerkonten zurückgesetzt. Bitte fordert über Passwort vergessen ein neues Passwort an."
|
||||||
|
color="rose"
|
||||||
|
variant="outline"
|
||||||
|
class="my-5"
|
||||||
|
>
|
||||||
|
</UAlert>
|
||||||
|
|
||||||
<UAuthForm
|
<UAuthForm
|
||||||
title="Login"
|
title="Login"
|
||||||
description="Geben Sie Ihre Anmeldedaten ein um Zugriff auf Ihren Account zu erhalten."
|
description="Geben Sie Ihre Anmeldedaten ein um Zugriff auf Ihren Account zu erhalten."
|
||||||
@@ -131,29 +58,13 @@ const onSubmit = async (data) => {
|
|||||||
placeholder: 'Dein Passwort'
|
placeholder: 'Dein Passwort'
|
||||||
}]"
|
}]"
|
||||||
:loading="false"
|
:loading="false"
|
||||||
@submit="onSubmit"
|
@submit="doLogin"
|
||||||
:providers="[{label: 'MS365',icon: 'i-simple-icons-microsoft',color: 'gray',click: authenticateWithAzure}]"
|
|
||||||
:submit-button="{label: 'Weiter'}"
|
:submit-button="{label: 'Weiter'}"
|
||||||
divider="oder"
|
divider="oder"
|
||||||
>
|
>
|
||||||
|
<template #password-hint>
|
||||||
|
<NuxtLink to="/password-reset" class="text-primary font-medium">Passwort vergessen?</NuxtLink>
|
||||||
|
</template>
|
||||||
</UAuthForm>
|
</UAuthForm>
|
||||||
</UCard>
|
</UCard>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
#loginSite {
|
|
||||||
display: flex;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#loginForm {
|
|
||||||
width: 30vw;
|
|
||||||
height: 30vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -6,7 +6,7 @@ definePageMeta({
|
|||||||
layout: 'mobile'
|
layout: 'mobile'
|
||||||
})
|
})
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
//const profileStore = useProfileStore()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ const profileStore = useProfileStore()
|
|||||||
>
|
>
|
||||||
<display-open-tasks/>
|
<display-open-tasks/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>
|
||||||
<UDashboardCard
|
<!--<UDashboardCard
|
||||||
title="Anwesenheit"
|
title="Anwesenheit"
|
||||||
>
|
>
|
||||||
<display-running-working-time/>
|
<display-running-working-time/>
|
||||||
@@ -36,7 +36,7 @@ const profileStore = useProfileStore()
|
|||||||
v-if="profileStore.ownTenant.features.accounting"
|
v-if="profileStore.ownTenant.features.accounting"
|
||||||
>
|
>
|
||||||
<display-open-balances/>
|
<display-open-balances/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>-->
|
||||||
<UDashboardCard
|
<UDashboardCard
|
||||||
title="Projekte"
|
title="Projekte"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ definePageMeta({
|
|||||||
layout: 'mobile',
|
layout: 'mobile',
|
||||||
})
|
})
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -65,17 +65,23 @@ const profileStore = useProfileStore()
|
|||||||
>
|
>
|
||||||
Objekte
|
Objekte
|
||||||
</UButton>
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
class="w-full my-1"
|
||||||
|
@click="auth.logout()"
|
||||||
|
color="rose"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
Abmelden
|
||||||
|
</UButton>
|
||||||
|
|
||||||
<UDivider class="my-5">Unternehmen wechseln</UDivider>
|
<UDivider class="my-5">Unternehmen wechseln</UDivider>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-row justify-between my-3" v-for="tenant in auth.tenants">
|
||||||
|
<span class="text-left">{{tenant.name}}</span>
|
||||||
<UButton
|
<UButton
|
||||||
v-for="option in profileStore.ownProfiles"
|
@click="auth.switchTenant(tenant.id)"
|
||||||
class="my-1"
|
>Wechseln</UButton>
|
||||||
variant="outline"
|
</div>
|
||||||
@click="profileStore.changeProfile(option.id)"
|
|
||||||
>
|
|
||||||
{{profileStore.tenants.find(i => i.id === option.tenant).name}}
|
|
||||||
</UButton>
|
|
||||||
|
|
||||||
|
|
||||||
</UDashboardPanelContent>
|
</UDashboardPanelContent>
|
||||||
|
|||||||
62
pages/password-change.vue
Normal file
62
pages/password-change.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
layout: "notLoggedIn"
|
||||||
|
})
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const doChange = async (data:any) => {
|
||||||
|
try {
|
||||||
|
const res = await useNuxtApp().$api("/api/auth/password/change", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
old_password: data.oldPassword,
|
||||||
|
new_password: data.newPassword,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Weiterleiten nach erfolgreichem Login
|
||||||
|
toast.add({title:"Ändern erfolgreich"})
|
||||||
|
await auth.logout()
|
||||||
|
return navigateTo("/login")
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.add({title:"Es gab ein Problem beim ändern",color:"rose"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UCard class="max-w-sm w-full mx-auto mt-5">
|
||||||
|
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UAuthForm
|
||||||
|
title="Passwort zurücksetzen"
|
||||||
|
description="Geben Sie Ihre E-Mail ein um ein neues Passwort per E-Mail zu erhalten."
|
||||||
|
align="bottom"
|
||||||
|
:fields="[{
|
||||||
|
name: 'oldPassword',
|
||||||
|
label: 'Altes Passwort',
|
||||||
|
type: 'password',
|
||||||
|
placeholder: 'Dein altes Passwort'
|
||||||
|
},{
|
||||||
|
name: 'newPassword',
|
||||||
|
label: 'Neues Passwort',
|
||||||
|
type: 'password',
|
||||||
|
placeholder: 'Dein neues Passwort'
|
||||||
|
}]"
|
||||||
|
:loading="false"
|
||||||
|
@submit="doChange"
|
||||||
|
:submit-button="{label: 'Ändern'}"
|
||||||
|
divider="oder"
|
||||||
|
>
|
||||||
|
</UAuthForm>
|
||||||
|
</UCard>
|
||||||
|
</template>
|
||||||
55
pages/password-reset.vue
Normal file
55
pages/password-reset.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
layout: "notLoggedIn"
|
||||||
|
})
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const doReset = async (data:any) => {
|
||||||
|
try {
|
||||||
|
const res = await useNuxtApp().$api("/auth/password/reset", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
email: data.email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Weiterleiten nach erfolgreichem Login
|
||||||
|
toast.add({title:"Zurücksetzen erfolgreich"})
|
||||||
|
return navigateTo("/login")
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.add({title:"Problem beim zurücksetzen",color:"rose"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UCard class="max-w-sm w-full mx-auto mt-5">
|
||||||
|
|
||||||
|
<UColorModeImage
|
||||||
|
light="/Logo.png"
|
||||||
|
dark="/Logo_Dark.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UAuthForm
|
||||||
|
title="Passwort zurücksetzen"
|
||||||
|
description="Geben Sie Ihre E-Mail ein um ein neues Passwort per E-Mail zu erhalten."
|
||||||
|
align="bottom"
|
||||||
|
:fields="[{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
label: 'Email',
|
||||||
|
placeholder: 'Deine E-Mail Adresse'
|
||||||
|
}]"
|
||||||
|
:loading="false"
|
||||||
|
@submit="doReset"
|
||||||
|
:submit-button="{label: 'Zurücksetzen'}"
|
||||||
|
divider="oder"
|
||||||
|
>
|
||||||
|
</UAuthForm>
|
||||||
|
</UCard>
|
||||||
|
</template>
|
||||||
@@ -3,13 +3,21 @@
|
|||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const items = ref([])
|
||||||
|
|
||||||
|
const setupPage = async () => {
|
||||||
|
items.value = (await useNuxtApp().$api("/api/tenant/users")).users
|
||||||
|
items.value = items.value.map(i => i.profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPage()
|
||||||
|
|
||||||
const templateColumns = [
|
const templateColumns = [
|
||||||
{
|
{
|
||||||
key: 'employeeNumber',
|
key: 'employee_number',
|
||||||
label: "MA-Nummer",
|
label: "MA-Nummer",
|
||||||
},{
|
},{
|
||||||
key: 'fullName',
|
key: 'full_name',
|
||||||
label: "Name",
|
label: "Name",
|
||||||
},{
|
},{
|
||||||
key: "email",
|
key: "email",
|
||||||
@@ -26,6 +34,7 @@
|
|||||||
<template #right>
|
<template #right>
|
||||||
<UButton
|
<UButton
|
||||||
@click="router.push(`/profiles/create`)"
|
@click="router.push(`/profiles/create`)"
|
||||||
|
disabled
|
||||||
>
|
>
|
||||||
+ Mitarbeiter
|
+ Mitarbeiter
|
||||||
</UButton>
|
</UButton>
|
||||||
@@ -33,8 +42,7 @@
|
|||||||
</UDashboardNavbar>
|
</UDashboardNavbar>
|
||||||
|
|
||||||
<UTable
|
<UTable
|
||||||
:rows="profileStore.profiles"
|
:rows="items"
|
||||||
@select="(item) => router.push(`/profiles/show/${item.id}`)"
|
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'backspace': () => {
|
'backspace': () => {
|
||||||
@@ -23,8 +21,6 @@ defineShortcuts({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const openTab = ref(0)
|
const openTab = ref(0)
|
||||||
const dataStore = useDataStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -55,9 +51,9 @@ const setKeys = () => {
|
|||||||
const setupPage = async() => {
|
const setupPage = async() => {
|
||||||
|
|
||||||
if(mode.value === "show" ){
|
if(mode.value === "show" ){
|
||||||
itemInfo.value = await useSupabaseSelectSingle("projecttypes",route.params.id,"*")
|
itemInfo.value = await useEntities("projecttypes").selectSingle(route.params.id,"*")
|
||||||
} else if (mode.value === "edit") {
|
} else if (mode.value === "edit") {
|
||||||
itemInfo.value = await useSupabaseSelectSingle("projecttypes",route.params.id,"*")
|
itemInfo.value = await useEntities("projecttypes").selectSingle(route.params.id,"*")
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mode.value === "create") {
|
if(mode.value === "create") {
|
||||||
@@ -101,13 +97,13 @@ const addPhase = () => {
|
|||||||
<template #right>
|
<template #right>
|
||||||
<UButton
|
<UButton
|
||||||
v-if="mode === 'edit'"
|
v-if="mode === 'edit'"
|
||||||
@click="dataStore.updateItem('projecttypes',itemInfo,oldItemInfo)"
|
@click="useEntities('projecttypes').update(itemInfo.id, itemInfo)"
|
||||||
>
|
>
|
||||||
Speichern
|
Speichern
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton
|
<UButton
|
||||||
v-else-if="mode === 'create'"
|
v-else-if="mode === 'create'"
|
||||||
@click="dataStore.createNewItem('projecttypes', itemInfo)"
|
@click="useEntities('projecttypes').create( itemInfo)"
|
||||||
>
|
>
|
||||||
Erstellen
|
Erstellen
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'/': () => {
|
'/': () => {
|
||||||
@@ -36,14 +34,14 @@ defineShortcuts({
|
|||||||
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const tempStore = useTempStore()
|
||||||
|
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const selectedItem = ref(0)
|
const selectedItem = ref(0)
|
||||||
|
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
items.value = await useSupabaseSelect("projecttypes","*")
|
items.value = await useEntities("projecttypes").select()
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
@@ -58,7 +56,7 @@ const templateColumns = [
|
|||||||
const selectedColumns = ref(templateColumns)
|
const selectedColumns = ref(templateColumns)
|
||||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||||
|
|
||||||
const searchString = ref("")
|
const searchString = ref(tempStore.searchStrings["projecttypes"] || '')
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
const filteredRows = computed(() => {
|
||||||
return useListFilter(searchString.value, items.value)
|
return useListFilter(searchString.value, items.value)
|
||||||
@@ -77,6 +75,7 @@ const filteredRows = computed(() => {
|
|||||||
placeholder="Suche..."
|
placeholder="Suche..."
|
||||||
class="hidden lg:block"
|
class="hidden lg:block"
|
||||||
@keydown.esc="$event.target.blur()"
|
@keydown.esc="$event.target.blur()"
|
||||||
|
@change="tempStore.modifySearchString('projecttypes',searchString)"
|
||||||
>
|
>
|
||||||
<template #trailing>
|
<template #trailing>
|
||||||
<UKbd value="/"/>
|
<UKbd value="/"/>
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||||
import DocumentList from "~/components/DocumentList.vue";
|
|
||||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
|
||||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
defineShortcuts({
|
||||||
'backspace': () => {
|
'backspace': () => {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
|
|||||||
@@ -30,13 +30,7 @@ const checkBIC = async () => {
|
|||||||
|
|
||||||
const generateLink = async (bankId) => {
|
const generateLink = async (bankId) => {
|
||||||
try {
|
try {
|
||||||
/*const {data,error} = await supabase.functions.invoke(`bankstatement_gateway`,{
|
|
||||||
body: {
|
|
||||||
method: "generateLink",
|
|
||||||
institutionId: bankData.value.id,
|
|
||||||
tenant: profileStore.currentTenant
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
const link = await useFunctions().useBankingGenerateLink(bankId || bankData.value.id)
|
const link = await useFunctions().useBankingGenerateLink(bankId || bankData.value.id)
|
||||||
|
|
||||||
await navigateTo(link, {
|
await navigateTo(link, {
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const items = [{
|
const items = [{
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar >
|
|
||||||
<template #left>
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-chevron-left"
|
|
||||||
variant="outline"
|
|
||||||
@click="router.push(`/settings/labels`)"
|
|
||||||
>
|
|
||||||
Labels
|
|
||||||
</UButton>
|
|
||||||
</template>
|
|
||||||
<template #center>
|
|
||||||
<h1
|
|
||||||
v-if="itemInfo"
|
|
||||||
class="text-xl font-medium"
|
|
||||||
>{{itemInfo.name ? `Label: ${itemInfo.name}` : (mode === 'create' ? 'Label erstellen' : 'Label bearbeiten')}}</h1>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
|
|
||||||
<UTabs
|
|
||||||
:items="[{label: 'Informationen'}]"
|
|
||||||
v-if="mode === 'show' && itemInfo"
|
|
||||||
class="p-5"
|
|
||||||
v-model="openTab"
|
|
||||||
>
|
|
||||||
<template #item="{item}">
|
|
||||||
<UCard class="mt-5">
|
|
||||||
<div
|
|
||||||
v-if="item.label === 'Informationen'"
|
|
||||||
class="flex flex-row"
|
|
||||||
>
|
|
||||||
<div class="w-1/2 mr-5">
|
|
||||||
<UDivider>Allgemeines</UDivider>
|
|
||||||
<Toolbar>
|
|
||||||
<UButton @click="usePrintLabel('0dbe30f3-3008-4cde-8a7c-e785b1c22bfc','ZD411',useGenerateZPL(itemInfo.handlebarsZPL,{barcode:'XXX'}))">Test Druck</UButton>
|
|
||||||
|
|
||||||
</Toolbar>
|
|
||||||
<p>Name: {{itemInfo.name}}</p>
|
|
||||||
<p>Breite in Zoll: {{itemInfo.widthInch}}"</p>
|
|
||||||
<p>Höhe in Zoll: {{itemInfo.heightInch}}"</p>
|
|
||||||
<p>ZPL:</p>
|
|
||||||
<pre>{{itemInfo.handlebarsZPL}}</pre>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="w-1/2">
|
|
||||||
<UDivider>Vorschau</UDivider>
|
|
||||||
<img
|
|
||||||
class="mx-auto mt-5"
|
|
||||||
v-if="demoZPL"
|
|
||||||
:src="`https://api.labelary.com/v1/printers/8dpmm/labels/${itemInfo.widthInch}x${itemInfo.heightInch}/0/${demoZPL}`"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</UCard>
|
|
||||||
</template>
|
|
||||||
</UTabs>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'backspace': () => {
|
|
||||||
router.push("/settings/labels")
|
|
||||||
},
|
|
||||||
'arrowleft': () => {
|
|
||||||
if(openTab.value > 0){
|
|
||||||
openTab.value -= 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'arrowright': () => {
|
|
||||||
if(openTab.value < 3) {
|
|
||||||
openTab.value += 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const mode = useRoute().params.mode
|
|
||||||
const openTab = ref(0)
|
|
||||||
|
|
||||||
const itemInfo = ref({})
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
itemInfo.value = await useSupabaseSelectSingle("printLabels",useRoute().params.id,'*')
|
|
||||||
renderDemoZPL()
|
|
||||||
}
|
|
||||||
|
|
||||||
const demoZPL = ref("")
|
|
||||||
const renderDemoZPL = () => {
|
|
||||||
let template = Handlebars.compile(itemInfo.value.handlebarsZPL)
|
|
||||||
|
|
||||||
demoZPL.value = template({barcode: "XXX"})
|
|
||||||
}
|
|
||||||
|
|
||||||
const printLabel = async () => {
|
|
||||||
await supabase.from("printJobs").insert({
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
rawContent: useGenerateZPL(itemInfo.value.handlebarsZPL,{barcode:"XXX"}),
|
|
||||||
printerName: "ZD411",
|
|
||||||
printServer: "0dbe30f3-3008-4cde-8a7c-e785b1c22bfc"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
img {
|
|
||||||
border: 1px solid black
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar
|
|
||||||
title="Labels"
|
|
||||||
>
|
|
||||||
<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(`/settings/labels/create`)" disabled>+ Label</UButton>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<!-- <UDashboardToolbar>
|
|
||||||
|
|
||||||
</UDashboardToolbar>-->
|
|
||||||
|
|
||||||
<UTable
|
|
||||||
:rows="items"
|
|
||||||
:columns="columns"
|
|
||||||
@select="(i) => router.push(`/settings/labels/show/${i.id}`)"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel 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>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
defineShortcuts({
|
|
||||||
'/': () => {
|
|
||||||
//console.log(searchinput)
|
|
||||||
//searchinput.value.focus()
|
|
||||||
document.getElementById("searchinput").focus()
|
|
||||||
},
|
|
||||||
'+': () => {
|
|
||||||
router.push("/settings/labels/create")
|
|
||||||
},
|
|
||||||
'Enter': {
|
|
||||||
usingInput: true,
|
|
||||||
handler: () => {
|
|
||||||
router.push(`/settings/labels/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 router = useRouter()
|
|
||||||
|
|
||||||
const items = ref([])
|
|
||||||
const selectedItem = ref(0)
|
|
||||||
|
|
||||||
const setupPage = async () => {
|
|
||||||
items.value = await useSupabaseSelect("printLabels","*")
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPage()
|
|
||||||
|
|
||||||
const templateColumns = [{key: 'name',label:'Name'},{key: 'widthInch',label:'Breite in Zoll'},{key: 'heightInch',label:'Höhe in Zoll'}]
|
|
||||||
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,13 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
const auth = useAuthStore()
|
||||||
middleware: "auth"
|
|
||||||
})
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
customers: {
|
customers: {
|
||||||
@@ -45,16 +37,19 @@ const resources = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const numberRanges = ref(profileStore.ownTenant.numberRanges)
|
const numberRanges = ref(auth.activeTenantData.numberRanges)
|
||||||
|
|
||||||
const updateNumberRanges = async (range) => {
|
const updateNumberRanges = async (range) => {
|
||||||
|
|
||||||
const {data,error} = await supabase
|
const res = await useNuxtApp().$api(`/api/tenant/numberrange/${range}`,{
|
||||||
.from("tenants")
|
method: "PUT",
|
||||||
.update({numberRanges: numberRanges.value})
|
body: {
|
||||||
.eq('id',profileStore.currentTenant)
|
numberRange: numberRanges.value[range]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(res)
|
||||||
|
|
||||||
await profileStore.fetchOwnTenant()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -91,13 +86,13 @@ const updateNumberRanges = async (range) => {
|
|||||||
<td>
|
<td>
|
||||||
<UInput
|
<UInput
|
||||||
v-model="numberRanges[key].prefix"
|
v-model="numberRanges[key].prefix"
|
||||||
@change="updateNumberRanges"
|
@change="updateNumberRanges(key)"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<UInput
|
<UInput
|
||||||
v-model="numberRanges[key].nextNumber"
|
v-model="numberRanges[key].nextNumber"
|
||||||
@change="updateNumberRanges"
|
@change="updateNumberRanges(key)"
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
/>
|
/>
|
||||||
@@ -105,7 +100,7 @@ const updateNumberRanges = async (range) => {
|
|||||||
<td>
|
<td>
|
||||||
<UInput
|
<UInput
|
||||||
v-model="numberRanges[key].suffix"
|
v-model="numberRanges[key].suffix"
|
||||||
@change="updateNumberRanges"
|
@change="updateNumberRanges(key)"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const data = await supabase.from("profiles").select('* , tenants (id, name)')
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
let rights = {
|
|
||||||
createUser: {label: "Benutzer erstellen"},
|
|
||||||
modifyUser: {label: "Benutzer bearbeiten"},
|
|
||||||
deactivateUser: {label: "Benutzer sperren"},
|
|
||||||
createProject: {label: "Projekt erstellen"},
|
|
||||||
viewOwnProjects: {label: "Eigene Projekte sehen"},
|
|
||||||
viewAllProjects: {label: "Alle Projekte sehen"},
|
|
||||||
createTask: {label: "Aufgabe erstellen"},
|
|
||||||
viewOwnTasks: {label:"Eigene Aufgaben sehen"},
|
|
||||||
viewAllTasks: {label: "Alle Aufgaben sehen"},
|
|
||||||
trackOwnTime: {label:"Eigene Zeite erfassen"},
|
|
||||||
createOwnTime: {label:"Eigene Zeiten erstellen"},
|
|
||||||
createTime: {label:"Zeiten erstellen"},
|
|
||||||
viewOwnTimes: {label:"Eigene Zeiten anzeigen"},
|
|
||||||
viewTimes: {label:"Zeiten anzeigen"},
|
|
||||||
}
|
|
||||||
|
|
||||||
let roles = [
|
|
||||||
{
|
|
||||||
key: "tenantAdmin",
|
|
||||||
label: "Firmenadministrator",
|
|
||||||
rights: [
|
|
||||||
...Object.keys(rights)
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"worker",
|
|
||||||
label: "Monteur",
|
|
||||||
rights: [
|
|
||||||
"viewOwnProjects",
|
|
||||||
"createTasks",
|
|
||||||
"viewOwnTasks"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"manager",
|
|
||||||
label: "Vorarbeiter",
|
|
||||||
rights: [
|
|
||||||
"createProjects",
|
|
||||||
"viewOwnProjects",
|
|
||||||
"createTasks",
|
|
||||||
"viewOwnTasks",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"booker",
|
|
||||||
label: "Buchhalter",
|
|
||||||
rights: [
|
|
||||||
"createTasks",
|
|
||||||
"viewOwnTasks",
|
|
||||||
"createTime",
|
|
||||||
"viewAllTimes"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user