Merge branch 'dev' into beta

This commit is contained in:
2025-04-12 12:31:53 +02:00
11 changed files with 175 additions and 194 deletions

View File

@@ -42,7 +42,7 @@ const showFile = (file) => {
<iframe <iframe
:src="`${documentData.url}#toolbar=0&navpanes=0&scrollbar=0`" :src="`${documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
class="previewEmbed" class="previewEmbed"
v-if="documentData.path.includes('pdf')" v-if="documentData.path.toLowerCase().includes('pdf')"
loading="lazy" loading="lazy"
/> />
<img <img

View File

@@ -24,6 +24,7 @@ const emit = defineEmits(["updateNeeded"])
const folders = ref([]) const folders = ref([])
const filetypes = ref([]) const filetypes = ref([])
const documentboxes = ref([])
const setup = async () => { const setup = async () => {
const {data} = await supabase.from("folders").select().eq("tenant",useProfileStore().currentTenant) const {data} = await supabase.from("folders").select().eq("tenant",useProfileStore().currentTenant)
@@ -55,6 +56,7 @@ const setup = async () => {
}) })
filetypes.value = await useSupabaseSelect("filetags") filetypes.value = await useSupabaseSelect("filetags")
documentboxes.value = await useSupabaseSelect("documentboxes")
} }
setup() setup()
@@ -180,7 +182,7 @@ const moveFile = async () => {
<template> <template>
<UModal fullscreen > <UModal fullscreen >
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }"> <UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }" class="h-full">
<template #header> <template #header>
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -200,7 +202,7 @@ const moveFile = async () => {
class="bigPreview" class="bigPreview"
:data="`${props.documentData.url}#toolbar=0&navpanes=0&scrollbar=0`" :data="`${props.documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
type="application/pdf" type="application/pdf"
v-if="props.documentData.path.includes('pdf')" v-if="props.documentData.path.toLowerCase().includes('pdf')"
/> />
<img <img
@@ -371,6 +373,18 @@ const moveFile = async () => {
@change="updateDocument" @change="updateDocument"
/> />
</InputGroup> </InputGroup>
<UDivider class="my-5">Dokumentenbox</UDivider>
<InputGroup class="w-full">
<USelectMenu
class="flex-auto"
v-model="props.documentData.documentbox"
value-attribute="id"
option-attribute="key"
:options="documentboxes"
@change="updateDocument"
/>
</InputGroup>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,7 @@
<script setup > <script setup >
import DocumentUploadModal from "~/components/DocumentUploadModal.vue";
const props = defineProps({ const props = defineProps({
type: { type: {
type: String type: String
@@ -13,103 +15,20 @@ const {type, elementId} = props
const emit = defineEmits(["uploadFinished"]) const emit = defineEmits(["uploadFinished"])
const dataStore = useDataStore() const modal = useModal()
const profileStore = useProfileStore()
const uploadModalOpen = ref(false)
const uploadInProgress = ref(false)
const fileUploadFormData = ref({
project: null,
tenant: profileStore.currentTenant
})
const availableTags = ref([])
const selectedTags = ref([])
const setup = async () => {
availableTags.value = await useSupabaseSelect("filetags")
}
setup()
const openModal = () => { const openModal = () => {
uploadModalOpen.value = true let fileProps = {folder: null, type: null, typeEnabled: true}
}
const uploadFiles = async () => { fileProps[props.type] = props.elementId
uploadInProgress.value = true;
let fileData = fileUploadFormData.value console.log(fileProps)
fileData[type] = elementId
await useFiles().uploadFiles(fileData, document.getElementById("fileUploadInput").files,selectedTags.value,true) modal.open(DocumentUploadModal,{fileData: fileProps, onUploadFinished: () => emit("uploadFinished")})
uploadModalOpen.value = false;
uploadInProgress.value = false;
emit("uploadFinished")
} }
</script> </script>
<template> <template>
<USlideover
v-model="uploadModalOpen"
>
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }" class="h-full">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Datei hochladen
</h3>
<UButton
color="gray"
variant="ghost"
icon="i-heroicons-x-mark-20-solid"
class="-my-1"
@click="uploadModalOpen = false"
:disabled="uploadInProgress"
/>
</div>
</template>
<UFormGroup
label="Datei:"
>
<UInput
type="file"
id="fileUploadInput"
multiple
accept="image/jpeg, image/png, image/gif, application/pdf"
/>
</UFormGroup>
<UFormGroup
label="Tags:"
class="mt-3"
>
<USelectMenu
multiple
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suchen..."
:options="availableTags"
v-model="selectedTags"
>
<template #label>
<span v-if="selectedTags.length > 0">{{selectedTags.map(i => availableTags.find(x => x.id === i).name).join(", ")}}</span>
<span v-else>Keine Tags ausgewählt</span>
</template>
</USelectMenu>
</UFormGroup>
<template #footer>
<UButton
@click="uploadFiles"
:loading="uploadInProgress"
>Hochladen</UButton>
</template>
</UCard>
</USlideover>
<UButton <UButton
@click="openModal" @click="openModal"
icon="i-heroicons-arrow-up-tray" icon="i-heroicons-arrow-up-tray"

View File

@@ -38,12 +38,13 @@ defineShortcuts({
const emit = defineEmits(["updateNeeded"]) const emit = defineEmits(["updateNeeded"])
const router = useRouter() const router = useRouter()
const route = useRoute()
const dataStore = useDataStore() const dataStore = useDataStore()
const modal = useModal() const modal = useModal()
const dataType = dataStore.dataTypes[type] const dataType = dataStore.dataTypes[type]
const openTab = ref(0) const openTab = ref(route.query.tabIndex || 0)
@@ -88,6 +89,10 @@ const getAvailableQueryStringData = (keys) => {
} }
const onTabChange = (index) => {
router.push(`${router.currentRoute.value.path}?tabIndex=${index}`)
}
</script> </script>
<template> <template>
@@ -188,6 +193,7 @@ const getAvailableQueryStringData = (keys) => {
v-if="props.item.id && platform !== 'mobile'" v-if="props.item.id && platform !== 'mobile'"
class="p-5" class="p-5"
v-model="openTab" v-model="openTab"
@change="onTabChange"
> >
<template #item="{item:tab}"> <template #item="{item:tab}">
<div v-if="tab.label === 'Informationen'" class="flex flex-row"> <div v-if="tab.label === 'Informationen'" class="flex flex-row">

View File

@@ -46,7 +46,7 @@ setup()
</template> </template>
<Toolbar> <Toolbar>
<DocumentUpload <DocumentUpload
:type="props.type.substring(0,props.type.length-1)" :type="props.topLevelType.substring(0,props.topLevelType.length-1)"
:element-id="props.item.id" :element-id="props.item.id"
@uploadFinished="emit('updateNeeded')" @uploadFinished="emit('updateNeeded')"
/> />

View File

@@ -27,8 +27,9 @@ 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="props.topLevelType.substring(0,props.topLevelType.length-1)" :type="dataType.historyItemHolder"
v-if="props.item.id" v-if="props
.item.id"
:element-id="props.item.id" :element-id="props.item.id"
render-headline render-headline
/> />

View File

@@ -54,10 +54,26 @@ const links = computed(() => {
to: "/standardEntity/events", to: "/standardEntity/events",
icon: "i-heroicons-calendar-days" icon: "i-heroicons-calendar-days"
}] : [], }] : [],
/*{
label: "Dateien",
to: "/files",
icon: "i-heroicons-document"
},*/
]
},
{
label: "Dokumente",
icon: "i-heroicons-rectangle-stack",
defaultOpen: false,
children: [
{ {
label: "Dateien", label: "Dateien",
to: "/files", to: "/files",
icon: "i-heroicons-document" icon: "i-heroicons-document"
},{
label: "Boxen",
to: "/standardEntity/documentboxes",
icon: "i-heroicons-archive-box"
}, },
] ]
}, },

View File

@@ -246,6 +246,18 @@ export const useRole = () => {
label: "Buchungskonten erstellen", label: "Buchungskonten erstellen",
parent: "ownaccounts" parent: "ownaccounts"
}, },
documentboxes: {
label: "Dokuemntenboxen",
showToAllUsers: false
},
"documentboxes-viewAll": {
label: "Alle Dokuemntenboxen einsehen",
parent: "documentboxesx"
},
"documentboxes-create": {
label: "Dokuemntenboxen erstellen",
parent: "documentboxes"
},
"inventory": { "inventory": {
label: "Lager", label: "Lager",
}, },

View File

@@ -1,6 +1,7 @@
<script setup> <script setup>
import dayjs from "dayjs"; import dayjs from "dayjs";
import {useSupabaseSelect} from "~/composables/useSupabase.js";
const supabase = useSupabaseClient() const supabase = useSupabaseClient()
const route = useRoute() const route = useRoute()
@@ -8,21 +9,57 @@ const router = useRouter()
const profileStore = useProfileStore() const profileStore = useProfileStore()
const itemInfo = ref(null) const itemInfo = ref(null)
const statementallocations = ref(null) const statementallocations = 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 supabase.from("accounts").select("*").eq("id",route.params.id).single()).data
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 supabase.from("statementallocations").select("*, bs_id(*)").eq("account", route.params.id).eq("tenant",profileStore.currentTenant).order("created_at",{ascending: true})).data
incominginvoices.value = (await useSupabaseSelect("incominginvoices", "*, vendor(*)")).filter(i => i.accounts.find(x => x.account == route.params.id))
} }
setup() setup()
const selectAllocation = (allocation) => { const selectAllocation = (allocation) => {
if(allocation.bs_id) { if(allocation.type === "statementallocation") {
router.push(`/banking/statements/edit/${allocation.bs_id.id}`) router.push(`/banking/statements/edit/${allocation.bs_id.id}`)
} else if(allocation.type === "incominginvoice") {
router.push(`/incominginvoices/show/${allocation.incominginvoiceid}`)
} }
} }
const renderedAllocations = computed(() => {
let tempstatementallocations = statementallocations.value.map(i => {
return {
...i,
type: "statementallocation",
date: i.bs_id.date,
partner: i.bs_id ? (i.bs_id.debName ? i.bs_id.debName : (i.bs_id.credName ? i.bs_id.credName : '')) : ''
}
})
let incominginvoicesallocations = []
incominginvoices.value.forEach(i => {
incominginvoicesallocations.push(...i.accounts.filter(x => x.account == route.params.id).map(x => {
return {
...x,
incominginvoiceid: i.id,
type: "incominginvoice",
amount: x.amountGross ? x.amountGross : x.amountNet,
date: i.date,
partner: i.vendor.name,
description: i.description,
color: i.expense ? "red" : "green"
}
}))
})
return [...tempstatementallocations, ... incominginvoicesallocations]
})
</script> </script>
@@ -76,21 +113,18 @@ const selectAllocation = (allocation) => {
<UCard class="mt-5" v-if="item.label === 'Buchungen'"> <UCard class="mt-5" v-if="item.label === 'Buchungen'">
<UTable <UTable
v-if="statementallocations" v-if="statementallocations"
:rows="statementallocations" :rows="renderedAllocations"
:columns="[{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}]" :columns="[{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}]"
@select="(i) => selectAllocation(i)" @select="(i) => selectAllocation(i)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }" :empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }"
> >
<template #amount-data="{row}"> <template #amount-data="{row}">
<span class="text-right text-rose-600" v-if="row.amount < 0">{{useCurrency(row.amount)}}</span> <span class="text-right text-rose-600" v-if="row.amount < 0 || row.color === 'red'">{{useCurrency(row.amount)}}</span>
<span class="text-right text-primary-500" v-else-if="row.amount > 0">{{useCurrency(row.amount)}}</span> <span class="text-right text-primary-500" v-else-if="row.amount > 0 || row.color === 'green'">{{useCurrency(row.amount)}}</span>
<span v-else>{{useCurrency(row.amount)}}</span> <span v-else>{{useCurrency(row.amount)}}</span>
</template> </template>
<template #date-data="{row}"> <template #date-data="{row}">
{{row.bs_id ? dayjs(row.bs_id.date).format('DD.MM.YYYY') : ''}} {{row.date ? dayjs(row.date).format('DD.MM.YYYY') : ''}}
</template>
<template #partner-data="{row}">
{{row.bs_id ? (row.bs_id.debName ? row.bs_id.debName : (row.bs_id.credName ? row.bs_id.credName : '')) : ''}}
</template> </template>
<template #description-data="{row}"> <template #description-data="{row}">
{{row.description ? row.description : ''}} {{row.description ? row.description : ''}}

View File

@@ -56,7 +56,6 @@ const profileStore = useProfileStore()
const supabase = useSupabaseClient() const supabase = useSupabaseClient()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const slideover = useSlideover()
const modal = useModal() const modal = useModal()
dataStore.fetchDocuments() dataStore.fetchDocuments()
@@ -73,8 +72,6 @@ const fileUploadFormData = ref({
const files = useFiles() const files = useFiles()
let tags = dataStore.getDocumentTags
const displayMode = ref("list") const displayMode = ref("list")
const displayModes = ref([{label: 'Liste',key:'list', icon: 'i-heroicons-list-bullet'},{label: 'Kacheln',key:'rectangles', icon: 'i-heroicons-squares-2x2'}]) const displayModes = ref([{label: 'Liste',key:'list', icon: 'i-heroicons-list-bullet'},{label: 'Kacheln',key:'rectangles', icon: 'i-heroicons-squares-2x2'}])
@@ -107,8 +104,9 @@ const setupPage = async () => {
const dropZone = document.getElementById("drop_zone") const dropZone = document.getElementById("drop_zone")
dropZone.ondragover = function (event) { dropZone.ondragover = function (event) {
console.log(event) modal.open(DocumentUploadModal,{fileData: {folder: currentFolder.value.id, type: currentFolder.value.standardFiletype, typeEnabled: currentFolder.value.standardFiletypeIsOptional}, onUploadFinished: () => {
isDragTarget.value = true setupPage()
}})
event.preventDefault() event.preventDefault()
} }
@@ -120,9 +118,6 @@ const setupPage = async () => {
console.log("files dropped") console.log("files dropped")
event.preventDefault() event.preventDefault()
await uploadFiles(event.dataTransfer.files)
isDragTarget.value = false
setupPage()
} }
@@ -230,23 +225,6 @@ const createFolder = async () => {
} }
const uploadFiles = async (files) => {
uploadInProgress.value = true;
if(files) {
//await dataStore.uploadFiles({tags: ["Ablage"],tenant: profileStore.currentTenant,folder: currentFolder.value.id}, files, true)
await dataStore.uploadFiles({tags: ["Ablage"],tenant: profileStore.currentTenant}, files, true)
} else {
await dataStore.uploadFiles(fileUploadFormData.value, document.getElementById("fileUploadInput").files, true)
}
uploadModalOpen.value = false;
uploadInProgress.value = false;
}
const downloadSelected = async () => { const downloadSelected = async () => {
const bucket = "filesdev"; const bucket = "filesdev";
@@ -352,9 +330,7 @@ const selectAll = () => {
<UDashboardNavbar <UDashboardNavbar
title="Dateien" title="Dateien"
> ></UDashboardNavbar>
</UDashboardNavbar>
<UDashboardToolbar> <UDashboardToolbar>
<template #left> <template #left>
<UBreadcrumb <UBreadcrumb
@@ -375,7 +351,7 @@ const selectAll = () => {
</USelectMenu> </USelectMenu>
<UButton @click="modal.open(DocumentUploadModal,{fileData: {folder: currentFolder.id, type: currentFolder.standardFiletype, typeEnabled: currentFolder.standardFiletypeIsOptional}})">+ Datei</UButton> <UButton @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"
@@ -419,7 +395,7 @@ const selectAll = () => {
</UDashboardToolbar> </UDashboardToolbar>
<div id="drop_zone" class="h-full scrollList" > <div id="drop_zone" class="h-full scrollList" >
<div v-if="loaded"> <div v-if="loaded">
<UDashboardPanelContent v-if="!isDragTarget" > <UDashboardPanelContent>
<div v-if="displayMode === 'list'"> <div v-if="displayMode === 'list'">
<table class="w-full"> <table class="w-full">
<thead> <thead>
@@ -498,70 +474,10 @@ const selectAll = () => {
/> />
</div> </div>
</UDashboardPanelContent> </UDashboardPanelContent>
<UCard
class=" m-5"
v-else>
<template #header>
<p class="mx-auto">Dateien zum hochladen hierher ziehen</p>
</template>
</UCard>
</div> </div>
<UProgress animation="carousel" v-else class="w-5/6 mx-auto mt-5"/> <UProgress animation="carousel" v-else class="w-5/6 mx-auto mt-5"/>
</div> </div>
<USlideover
v-model="uploadModalOpen"
>
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
Datei Hochladen
</template>
<div class="h-full">
<UFormGroup
label="Datei:"
>
<UInput
type="file"
id="fileUploadInput"
multiple
/>
</UFormGroup>
<UFormGroup
label="Tags:"
class="mt-3"
>
<USelectMenu
multiple
searchable
searchable-placeholder="Suchen..."
option-attribute="name"
value-attribute="id"
:options="filetags"
v-model="fileUploadFormData.tags"
/>
</UFormGroup>
</div>
<template #footer>
<UButton
v-if="!uploadInProgress"
class="mt-3"
@click="uploadFiles"
>Hochladen</UButton>
<UProgress
v-else
animation="carousel"
/>
</template>
</UCard>
</USlideover>
</template> </template>
<style scoped> <style scoped>

View File

@@ -1528,6 +1528,69 @@ export const useDataStore = defineStore('data', () => {
} }
] ]
}, },
documentboxes: {
isArchivable: true,
label: "Dokumentenboxen",
labelSingle: "Dokumentenbox",
isStandardEntity: true,
supabaseSelectWithInformation: "*, space(*), files(*)",
redirect: true,
numberRangeHolder: "key",
historyItemHolder: "documentbox",
inputColumns: [
"Allgemeines",
],
filters:[{
name: "Archivierte ausblenden",
default: true,
"filterFunction": function (row) {
if(!row.archived) {
return true
} else {
return false
}
}
}],
templateColumns: [
{
key: "space",
label: "Aktueller Lagerplatz",
inputType: "select",
selectDataType: "spaces",
selectOptionAttribute: "name",
selectSearchAttributes: ['name'],
inputColumn: "Allgemeines",
component: space
},
{
key: "key",
label: "Nummer",
inputType: "text",
inputIsNumberRange: true,
inputColumn: "Allgemeines",
title: true,
required: true,
},
{
key: "profiles",
label: "Berechtigte Benutzer",
inputType: "select",
selectDataType: "profiles",
selectOptionAttribute: "fullName",
selectSearchAttributes: ['fullName'],
selectMultiple: true,
component: profiles
},
],
showTabs: [
{
label: 'Informationen',
}, {
label: 'Dateien',
}
]
},
services: { services: {
isArchivable: true, isArchivable: true,
label: "Leistungen", label: "Leistungen",