Merge branch 'devCorrected' into 'beta'
Added select-special See merge request fedeo/software!2
This commit is contained in:
8
app.vue
8
app.vue
@@ -1,12 +1,7 @@
|
||||
<script setup>
|
||||
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) => {
|
||||
console.log('Breakpoint updated:', oldBreakpoint, '->', newBreakpoint)
|
||||
@@ -70,7 +65,6 @@ useSeoMeta({
|
||||
<UNotifications :class="platform === 'mobile' ? ['mb-14'] : []"/>
|
||||
<USlideovers />
|
||||
<UModals/>
|
||||
<VitePwaManifest/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -66,18 +66,18 @@ const showFile = (file) => {
|
||||
|
||||
<style scoped>
|
||||
.documentListItem {
|
||||
display:block;
|
||||
display: block;
|
||||
width: 15vw;
|
||||
aspect-ratio: 1 / 1.414;
|
||||
padding:1em;
|
||||
padding: 1em;
|
||||
margin: 0.7em;
|
||||
border: 1px solid lightgrey;
|
||||
border-radius: 15px;
|
||||
transition: box-shadow 0.2s ease; /* für smooth hover */
|
||||
}
|
||||
|
||||
.documentListItem:hover {
|
||||
border: 1px solid #69c350;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); /* sanfter Shadow beim Hover */
|
||||
}
|
||||
|
||||
.previewEmbed {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
const toast = useToast()
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const modal = useModal()
|
||||
const props = defineProps({
|
||||
@@ -27,7 +26,7 @@ const filetypes = ref([])
|
||||
const documentboxes = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
const {data} = await supabase.from("folders").select().eq("tenant",useProfileStore().currentTenant)
|
||||
const data = await useEntities("folders").select()
|
||||
|
||||
data.forEach(folder => {
|
||||
let name = folder.name
|
||||
@@ -55,20 +54,12 @@ const setup = async () => {
|
||||
}
|
||||
})
|
||||
|
||||
filetypes.value = await useSupabaseSelect("filetags")
|
||||
documentboxes.value = await useSupabaseSelect("documentboxes")
|
||||
filetypes.value = await useEntities("filetags").select()
|
||||
documentboxes.value = await useEntities("documentboxes").select()
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
|
||||
//Functions
|
||||
const openDocument = async () => {
|
||||
//selectedDocument.value = doc
|
||||
openShowModal.value = true
|
||||
console.log("open")
|
||||
}
|
||||
|
||||
const updateDocument = async () => {
|
||||
const {url, ...objData} = props.documentData
|
||||
delete objData.url
|
||||
@@ -91,12 +82,7 @@ const updateDocument = async () => {
|
||||
|
||||
console.log(objData)
|
||||
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("files")
|
||||
.update(objData)
|
||||
.eq('id',objData.id)
|
||||
.select()
|
||||
const {data,error} = await useEntities("files").update(objData.id, objData)
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
@@ -114,13 +100,6 @@ const archiveDocument = async () => {
|
||||
props.documentData.archived = true
|
||||
await updateDocument()
|
||||
|
||||
const {data,error} = await supabase.from("historyitems").insert({
|
||||
createdBy: useProfileStore().activeProfile.id,
|
||||
tenant: useProfileStore().currentTenant,
|
||||
text: "Datei archiviert",
|
||||
file: props.documentData.id
|
||||
})
|
||||
|
||||
modal.close()
|
||||
emit("update")
|
||||
}
|
||||
@@ -139,19 +118,19 @@ const itemOptions = ref([])
|
||||
const idToAssign = ref(null)
|
||||
const getItemsBySelectedResource = async () => {
|
||||
if(resourceToAssign.value === "project") {
|
||||
itemOptions.value = await useSupabaseSelect("projects")
|
||||
itemOptions.value = await useEntities("projects").select()
|
||||
} else if(resourceToAssign.value === "customer") {
|
||||
itemOptions.value = await useSupabaseSelect("customers")
|
||||
itemOptions.value = await useEntities("customers").select()
|
||||
} else if(resourceToAssign.value === "vendor") {
|
||||
itemOptions.value = await useSupabaseSelect("vendors")
|
||||
itemOptions.value = await useEntities("vendors").select()
|
||||
} else if(resourceToAssign.value === "vehicle") {
|
||||
itemOptions.value = await useSupabaseSelect("vehicles")
|
||||
itemOptions.value = await useEntities("vehicles").select()
|
||||
} else if(resourceToAssign.value === "product") {
|
||||
itemOptions.value = await useSupabaseSelect("products")
|
||||
itemOptions.value = await useEntities("products").select()
|
||||
} else if(resourceToAssign.value === "plant") {
|
||||
itemOptions.value = await useSupabaseSelect("plants")
|
||||
itemOptions.value = await useEntities("plants").select()
|
||||
} else if(resourceToAssign.value === "contract") {
|
||||
itemOptions.value = await useSupabaseSelect("contracts")
|
||||
itemOptions.value = await useEntities("contracts").select()
|
||||
} else {
|
||||
itemOptions.value = []
|
||||
}
|
||||
@@ -165,20 +144,9 @@ const updateDocumentAssignment = async () => {
|
||||
|
||||
const folderToMoveTo = ref(null)
|
||||
const moveFile = async () => {
|
||||
console.log(folderToMoveTo.value)
|
||||
const {data,error} = await supabase
|
||||
.from("files")
|
||||
.update({folder: folderToMoveTo.value})
|
||||
.eq("id",props.documentData.id)
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
toast.add({title: "Fehler beim verschieben", color:"rose"})
|
||||
} else {
|
||||
toast.add({title: "Datei verschoben"})
|
||||
console.log(data)
|
||||
}
|
||||
const res = await useEntities("files").update(props.documentData.id, {folder: folderToMoveTo.value})
|
||||
|
||||
modal.close()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const uploadInProgress = ref(false)
|
||||
const availableFiletypes = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
availableFiletypes.value = await useSupabaseSelect("filetags")
|
||||
availableFiletypes.value = await useEntities("filetags").select()
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
@@ -48,17 +48,15 @@ defineShortcuts({
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const modal = useModal()
|
||||
|
||||
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
const openTab = ref(0)
|
||||
|
||||
const item = ref(JSON.parse(props.item))
|
||||
console.log(item.value)
|
||||
|
||||
@@ -156,7 +154,7 @@ const loadOptions = async () => {
|
||||
} else if(option.option === "units") {
|
||||
loadedOptions.value[option.option] = (await supabase.from("units").select()).data
|
||||
} 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){
|
||||
loadedOptions.value[option.option] = loadedOptions.value[option.option].filter(i => dataType.templateColumns.find(x => x.key === option.key).selectDataTypeFilter(i, item))
|
||||
@@ -210,7 +208,7 @@ const createItem = async () => {
|
||||
if(props.inModal) {
|
||||
ret = await dataStore.createNewItem(type,item.value,true)
|
||||
} else {
|
||||
ret = dataStore.createNewItem(type,item.value)
|
||||
ret = await useEntities(type).create(item.value)//dataStore.createNewItem(type,item.value)
|
||||
}
|
||||
|
||||
emit('returnData', ret)
|
||||
@@ -222,7 +220,8 @@ const updateItem = async () => {
|
||||
if(props.inModal) {
|
||||
ret = await dataStore.updateItem(type,item.value, oldItem.value,true)
|
||||
} 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)
|
||||
|
||||
@@ -4,6 +4,9 @@ import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
|
||||
import EntityTable from "~/components/EntityTable.vue";
|
||||
import EntityListMobile from "~/components/EntityListMobile.vue";
|
||||
|
||||
const { has } = usePermission()
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
required: true,
|
||||
@@ -15,9 +18,17 @@ const props = defineProps({
|
||||
},
|
||||
platform: {
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(["sort"]);
|
||||
|
||||
|
||||
const {type} = props
|
||||
|
||||
defineShortcuts({
|
||||
@@ -83,23 +94,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)
|
||||
})
|
||||
</script>
|
||||
@@ -139,7 +133,7 @@ const filteredRows = computed(() => {
|
||||
/>
|
||||
|
||||
<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`)"
|
||||
class="ml-3"
|
||||
>+ {{dataType.labelSingle}}</UButton>
|
||||
@@ -190,9 +184,11 @@ const filteredRows = computed(() => {
|
||||
/>
|
||||
<EntityTable
|
||||
v-else
|
||||
@sort="(i) => emit('sort',i)"
|
||||
:type="props.type"
|
||||
:columns="columns"
|
||||
:rows="filteredRows"
|
||||
:loading="props.loading"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -158,22 +158,6 @@ const onTabChange = (index) => {
|
||||
v-else-if="!props.inModal && platform === 'mobile'"
|
||||
: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>
|
||||
<div></div>
|
||||
</template>
|
||||
@@ -226,6 +210,7 @@ const onTabChange = (index) => {
|
||||
@updateNeeded="emit('updateNeeded')"
|
||||
:platform="platform"
|
||||
/>
|
||||
<!-- TODO Change Active Phase -->
|
||||
<EntityShowSubPhases
|
||||
:item="props.item"
|
||||
:top-level-type="type"
|
||||
@@ -234,14 +219,14 @@ const onTabChange = (index) => {
|
||||
@updateNeeded="emit('updateNeeded')"
|
||||
:platform="platform"
|
||||
/>
|
||||
<EntityShowSubCreatedDocuments
|
||||
<EntityShowSubCreatedDocuments
|
||||
:item="props.item"
|
||||
:top-level-type="type"
|
||||
v-else-if="tab.label === 'Ausgangsbelege'"
|
||||
:query-string-data="getAvailableQueryStringData()"
|
||||
:platform="platform"
|
||||
/>
|
||||
<EntityShowSubCostCentreReport
|
||||
<EntityShowSubCostCentreReport
|
||||
:top-level-type="type"
|
||||
:item="props.item"
|
||||
v-else-if="tab.label === 'Auswertung Kostenstelle'"
|
||||
@@ -259,13 +244,12 @@ const onTabChange = (index) => {
|
||||
v-else-if="tab.label === 'Zeiten'"
|
||||
:platform="platform"
|
||||
/>
|
||||
|
||||
|
||||
<EntityShowSub
|
||||
:item="props.item"
|
||||
:query-string-data="getAvailableQueryStringData()"
|
||||
:tab-label="tab.label"
|
||||
:top-level-type="type"
|
||||
:type="tab.key"
|
||||
v-else
|
||||
:platform="platform"
|
||||
/>
|
||||
|
||||
@@ -27,8 +27,6 @@ const props = defineProps({
|
||||
|
||||
let type = ref("")
|
||||
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const tempStore = useTempStore()
|
||||
|
||||
@@ -42,6 +40,7 @@ const columns = computed(() => dataType.templateColumns.filter((column) => !colu
|
||||
const loaded = ref(false)
|
||||
|
||||
const setup = () => {
|
||||
|
||||
if(!props.type && props.tabLabel ) {
|
||||
if(props.tabLabel === "Aufgaben") {
|
||||
type.value = "tasks"
|
||||
|
||||
@@ -61,7 +61,8 @@ const router = useRouter()
|
||||
const createddocuments = ref([])
|
||||
|
||||
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()
|
||||
|
||||
@@ -150,6 +151,7 @@ const selectItem = (item) => {
|
||||
<span>Ausgangsbelege</span>
|
||||
</template>
|
||||
<Toolbar>
|
||||
<!-- TODO Rendering when Screen is too small -->
|
||||
<UButton
|
||||
@click="invoiceDeliveryNotes"
|
||||
v-if="props.topLevelType === 'projects'"
|
||||
|
||||
@@ -31,7 +31,7 @@ const availableFiles = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
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')"
|
||||
/>
|
||||
</Toolbar>
|
||||
|
||||
<DocumentList
|
||||
:key="props.item.files.length"
|
||||
:documents="availableFiles"
|
||||
v-if="availableFiles.length > 0"
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
v-else
|
||||
icon="i-heroicons-x-mark"
|
||||
|
||||
@@ -27,9 +27,8 @@ const dataType = dataStore.dataTypes[props.topLevelType]
|
||||
<template>
|
||||
<UCard class="mt-5 scroll" :style="props.platform !== 'mobile' ? 'height: 80vh' : ''">
|
||||
<HistoryDisplay
|
||||
:type="dataType.historyItemHolder"
|
||||
v-if="props
|
||||
.item.id"
|
||||
:type="props.topLevelType"
|
||||
v-if="props.item.id"
|
||||
:element-id="props.item.id"
|
||||
render-headline
|
||||
/>
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
type: Array,
|
||||
@@ -40,9 +39,16 @@
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(["sort"]);
|
||||
|
||||
const dataStore = useDataStore()
|
||||
|
||||
const router = useRouter()
|
||||
@@ -50,12 +56,20 @@
|
||||
const dataType = dataStore.dataTypes[props.type]
|
||||
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const sort = ref({
|
||||
column: dataType.supabaseSortColumn || "date",
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
:rows="props.rows"
|
||||
:columns="props.columns"
|
||||
@@ -64,18 +78,26 @@
|
||||
@select="(i) => router.push(`/standardEntity/${type}/show/${i.id}`) "
|
||||
: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-slot:[`${column.key}-header`]="{row}">
|
||||
<span class="text-nowrap">{{column.label}}</span>
|
||||
</template>
|
||||
</template>-->
|
||||
<template #name-data="{row}">
|
||||
<span
|
||||
v-if="row.id === props.rows[selectedItem].id"
|
||||
class="text-primary-500 font-bold">{{row.name}}
|
||||
</span>
|
||||
class="text-primary-500 font-bold">
|
||||
<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>
|
||||
{{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>
|
||||
</template>
|
||||
<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-slot:[`${column.key}-data`]="{row}">
|
||||
<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>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
@@ -14,8 +14,9 @@ const props = defineProps({
|
||||
default: false
|
||||
}
|
||||
})
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
const toast = useToast()
|
||||
const showAddHistoryItemModal = ref(false)
|
||||
const colorMode = useColorMode()
|
||||
@@ -28,11 +29,12 @@ const setup = async () => {
|
||||
if(await useCapacitor().getIsPhone()) platform.value = "mobile"
|
||||
|
||||
if(props.type && props.elementId){
|
||||
items.value = (await supabase.from("historyitems").select().eq(props.type,props.elementId).order("created_at",{ascending: true})).data || []
|
||||
} else {
|
||||
//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 {
|
||||
items.value = (await supabase.from("historyitems").select().order("created_at",{ascending: true})).data || []
|
||||
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
setup()
|
||||
@@ -40,55 +42,24 @@ setup()
|
||||
|
||||
|
||||
const addHistoryItemData = ref({
|
||||
text: "",
|
||||
config: {
|
||||
type: props.type,
|
||||
id: props.elementId
|
||||
}
|
||||
text: ""
|
||||
})
|
||||
|
||||
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",
|
||||
body: addHistoryItemData.value
|
||||
})
|
||||
|
||||
const {data,error} = await supabase
|
||||
.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]
|
||||
addHistoryItemData.value = {}
|
||||
toast.add({title: "Eintrag erfolgreich erstellt"})
|
||||
showAddHistoryItemModal.value = false
|
||||
await setup()
|
||||
|
||||
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 = {}
|
||||
toast.add({title: "Eintrag erfolgreich erstellt"})
|
||||
showAddHistoryItemModal.value = false
|
||||
await setup()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -170,15 +141,15 @@ const renderText = (text) => {
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<UAvatar
|
||||
v-if="!item.createdBy"
|
||||
v-if="!item.created_by"
|
||||
:src="colorMode.value === 'light' ? '/Logo.png' : '/Logo_Dark.png' "
|
||||
/>
|
||||
<UAvatar
|
||||
:alt="profileStore.getProfileById(item.createdBy).fullName"
|
||||
:alt="item.created_by_profile?.full_name"
|
||||
v-else
|
||||
/>
|
||||
<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>
|
||||
<span v-html="renderText(item.text)"/><br>
|
||||
<span class="text-gray-500">{{dayjs(item.created_at).format("DD.MM.YY HH:mm")}}</span>
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
<script setup>
|
||||
import {useRole} from "~/composables/useRole.js";
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const {has} = usePermission()
|
||||
|
||||
const role = useRole()
|
||||
const links = computed(() => {
|
||||
return [
|
||||
... profileStore.currentTenant === 21 ? [{
|
||||
label: "Lieferschein Formular",
|
||||
to: "https://loar.ma-agrarservice.de/erfassung",
|
||||
icon: "i-heroicons-rectangle-stack",
|
||||
target: "_blank",
|
||||
}] : [],
|
||||
... profileStore.currentTenant === 5 ? [{
|
||||
...(auth.profile?.pinned_on_navigation || []).map(pin => {
|
||||
if(pin.type === "external") {
|
||||
return {
|
||||
label: pin.label,
|
||||
to: pin.link,
|
||||
icon: pin.icon,
|
||||
target: "_blank",
|
||||
pinned: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
... false ? [{
|
||||
label: "Support Tickets",
|
||||
to: "/support",
|
||||
icon: "i-heroicons-rectangle-stack",
|
||||
@@ -34,22 +40,22 @@ const links = computed(() => {
|
||||
icon: "i-heroicons-rectangle-stack",
|
||||
defaultOpen: false,
|
||||
children: [
|
||||
... role.checkRight("tasks") ? [{
|
||||
... has("tasks") ? [{
|
||||
label: "Aufgaben",
|
||||
to: "/standardEntity/tasks",
|
||||
icon: "i-heroicons-rectangle-stack"
|
||||
}] : [],
|
||||
... profileStore.ownTenant.features.planningBoard ? [{
|
||||
... true ? [{
|
||||
label: "Plantafel",
|
||||
to: "/calendar/timeline",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
... profileStore.ownTenant.features.calendar ? [{
|
||||
... true ? [{
|
||||
label: "Kalender",
|
||||
to: "/calendar/grid",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
... profileStore.ownTenant.features.calendar ? [{
|
||||
... true ? [{
|
||||
label: "Termine",
|
||||
to: "/standardEntity/events",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
@@ -86,7 +92,7 @@ const links = computed(() => {
|
||||
label: "E-Mail",
|
||||
to: "/email/new",
|
||||
icon: "i-heroicons-envelope"
|
||||
}, {
|
||||
}/*, {
|
||||
label: "Logbücher",
|
||||
to: "/communication/historyItems",
|
||||
icon: "i-heroicons-book-open"
|
||||
@@ -94,25 +100,25 @@ const links = computed(() => {
|
||||
label: "Chats",
|
||||
to: "/chats",
|
||||
icon: "i-heroicons-chat-bubble-left"
|
||||
}
|
||||
}*/
|
||||
]
|
||||
},
|
||||
... (role.checkRight("customers") || role.checkRight("vendors") || role.checkRight("contacts")) ? [{
|
||||
... (has("customers") || has("vendors") || has("contacts")) ? [{
|
||||
label: "Kontakte",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-user-group",
|
||||
children: [
|
||||
... role.checkRight("customers") ? [{
|
||||
... has("customers") ? [{
|
||||
label: "Kunden",
|
||||
to: "/standardEntity/customers",
|
||||
icon: "i-heroicons-user-group"
|
||||
}] : [],
|
||||
... role.checkRight("vendors") ? [{
|
||||
... has("vendors") ? [{
|
||||
label: "Lieferanten",
|
||||
to: "/standardEntity/vendors",
|
||||
icon: "i-heroicons-truck"
|
||||
}] : [],
|
||||
... role.checkRight("contacts") ? [{
|
||||
... has("contacts") ? [{
|
||||
label: "Ansprechpartner",
|
||||
to: "/standardEntity/contacts",
|
||||
icon: "i-heroicons-user-group"
|
||||
@@ -124,17 +130,17 @@ const links = computed(() => {
|
||||
defaultOpen:false,
|
||||
icon: "i-heroicons-user-group",
|
||||
children: [
|
||||
... profileStore.ownTenant.features.timeTracking ? [{
|
||||
... true ? [{
|
||||
label: "Projektzeiten",
|
||||
to: "/times",
|
||||
icon: "i-heroicons-clock"
|
||||
}] : [],
|
||||
... profileStore.ownTenant.features.workingTimeTracking ? [{
|
||||
... true ? [{
|
||||
label: "Anwesenheiten",
|
||||
to: "/workingtimes",
|
||||
icon: "i-heroicons-clock"
|
||||
}] : [],
|
||||
... role.checkRight("absencerequests") ? [{
|
||||
... has("absencerequests") ? [{
|
||||
label: "Abwesenheiten",
|
||||
to: "/standardEntity/absencerequests",
|
||||
icon: "i-heroicons-document-text"
|
||||
@@ -146,7 +152,7 @@ const links = computed(() => {
|
||||
},
|
||||
]
|
||||
},
|
||||
... profileStore.ownTenant.features.accounting ? [{
|
||||
... [{
|
||||
label: "Buchhaltung",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-chart-bar-square",
|
||||
@@ -182,8 +188,8 @@ const links = computed(() => {
|
||||
icon: "i-heroicons-document-text"
|
||||
},
|
||||
]
|
||||
},] : [],
|
||||
... role.checkRight("inventory") ? [{
|
||||
}],
|
||||
... has("inventory") ? [{
|
||||
label: "Lager",
|
||||
icon: "i-heroicons-puzzle-piece",
|
||||
defaultOpen: false,
|
||||
@@ -197,7 +203,7 @@ const links = computed(() => {
|
||||
to: "/inventory/stocks",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
},*/
|
||||
... role.checkRight("spaces") ? [{
|
||||
... has("spaces") ? [{
|
||||
label: "Lagerplätze",
|
||||
to: "/standardEntity/spaces",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
@@ -209,22 +215,22 @@ const links = computed(() => {
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-clipboard-document",
|
||||
children: [
|
||||
... role.checkRight("products") ? [{
|
||||
... has("products") ? [{
|
||||
label: "Artikel",
|
||||
to: "/standardEntity/products",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
}] : [],
|
||||
... role.checkRight("productcategories") ? [{
|
||||
... has("productcategories") ? [{
|
||||
label: "Artikelkategorien",
|
||||
to: "/standardEntity/productcategories",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
}] : [],
|
||||
... role.checkRight("services") ? [{
|
||||
... has("services") ? [{
|
||||
label: "Leistungen",
|
||||
to: "/standardEntity/services",
|
||||
icon: "i-heroicons-wrench-screwdriver"
|
||||
}] : [],
|
||||
... role.checkRight("servicecategories") ? [{
|
||||
... has("servicecategories") ? [{
|
||||
label: "Leistungskategorien",
|
||||
to: "/standardEntity/servicecategories",
|
||||
icon: "i-heroicons-wrench-screwdriver"
|
||||
@@ -239,70 +245,67 @@ const links = computed(() => {
|
||||
to: "/standardEntity/hourrates",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
... role.checkRight("vehicles") ? [{
|
||||
... has("vehicles") ? [{
|
||||
label: "Fahrzeuge",
|
||||
to: "/standardEntity/vehicles",
|
||||
icon: "i-heroicons-truck"
|
||||
}] : [],
|
||||
... role.checkRight("inventoryitems") ? [{
|
||||
... has("inventoryitems") ? [{
|
||||
label: "Inventar",
|
||||
to: "/standardEntity/inventoryitems",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
}] : [],
|
||||
... role.checkRight("inventoryitems") ? [{
|
||||
... has("inventoryitems") ? [{
|
||||
label: "Inventargruppen",
|
||||
to: "/standardEntity/inventoryitemgroups",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
}] : [],
|
||||
]
|
||||
},
|
||||
... role.checkRight("checks") ? [{
|
||||
label: "Überprüfungen",
|
||||
to: "/standardEntity/checks",
|
||||
icon: "i-heroicons-magnifying-glass"
|
||||
},] : [],
|
||||
... role.checkRight("projects") ? [{
|
||||
|
||||
... has("projects") ? [{
|
||||
label: "Projekte",
|
||||
to: "/standardEntity/projects",
|
||||
icon: "i-heroicons-clipboard-document-check"
|
||||
},] : [],
|
||||
... role.checkRight("contracts") ? [{
|
||||
... has("contracts") ? [{
|
||||
label: "Verträge",
|
||||
to: "/standardEntity/contracts",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
}] : [],
|
||||
... role.checkRight("plants") ? [{
|
||||
... has("plants") ? [{
|
||||
label: "Objekte",
|
||||
to: "/standardEntity/plants",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
},] : [],
|
||||
|
||||
... has("checks") ? [{
|
||||
label: "Überprüfungen",
|
||||
to: "/standardEntity/checks",
|
||||
icon: "i-heroicons-magnifying-glass"
|
||||
},] : [],
|
||||
{
|
||||
label: "Einstellungen",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-cog-8-tooth",
|
||||
children: [
|
||||
{
|
||||
label: "Abrechnung",
|
||||
to: "https://billing.stripe.com/p/login/cN29Eb32Vdx0gOk288",
|
||||
icon: "i-heroicons-document-currency-euro",
|
||||
target: "_blank"
|
||||
},{
|
||||
label: "Nummernkreise",
|
||||
to: "/settings/numberRanges",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
},/*{
|
||||
label: "Rollen",
|
||||
to: "/roles",
|
||||
icon: "i-heroicons-key"
|
||||
},{
|
||||
},*/{
|
||||
label: "E-Mail Konten",
|
||||
to: "/settings/emailAccounts",
|
||||
icon: "i-heroicons-envelope"
|
||||
icon: "i-heroicons-envelope",
|
||||
disabled: true
|
||||
},{
|
||||
label: "Bankkonten",
|
||||
to: "/settings/banking",
|
||||
icon: "i-heroicons-currency-euro"
|
||||
icon: "i-heroicons-currency-euro",
|
||||
disabled: true
|
||||
},{
|
||||
label: "Textvorlagen",
|
||||
to: "/settings/texttemplates",
|
||||
@@ -328,59 +331,126 @@ 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>
|
||||
|
||||
<template>
|
||||
|
||||
<div
|
||||
v-for="item in links"
|
||||
>
|
||||
<UAccordion
|
||||
v-if="item.children"
|
||||
:items="[item]"
|
||||
>
|
||||
<template #default="{item,index,open}">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
:color="item.children.find(i => route.path.includes(i.to)) ? 'primary' : 'gray'"
|
||||
:icon="item.icon"
|
||||
>
|
||||
{{item.label}}
|
||||
|
||||
<template #trailing>
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="w-5 h-5 ms-auto transform transition-transform duration-200"
|
||||
:class="[open && 'rotate-90']"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
<template #item="{item, open}">
|
||||
<div class="flex flex-col">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
:color="child.to === route.path ? 'primary' : 'gray'"
|
||||
:icon="child.icon"
|
||||
v-for="child in item.children"
|
||||
class="ml-4"
|
||||
:to="child.to"
|
||||
:target="child.target"
|
||||
>
|
||||
{{child.label}}
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
<!-- Standalone Buttons -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<UButton
|
||||
v-else
|
||||
variant="ghost"
|
||||
:color="item.to === route.path ? 'primary' : 'gray'"
|
||||
class="w-full"
|
||||
:icon="item.icon"
|
||||
:to="item.to"
|
||||
v-for="item in buttonItems"
|
||||
: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"
|
||||
>
|
||||
{{item.label}}
|
||||
<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"
|
||||
class="w-full"
|
||||
>
|
||||
{{ item.label }}
|
||||
<template #trailing>
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="w-5 h-5 ms-auto transform transition-transform duration-200"
|
||||
:class="[open && 'rotate-90']"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<div class="flex flex-col">
|
||||
<UButton
|
||||
v-for="child in item.children"
|
||||
:key="child.label"
|
||||
variant="ghost"
|
||||
:color="child.to === route.path ? 'primary' : 'gray'"
|
||||
: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"
|
||||
:key="child.label"
|
||||
variant="ghost"
|
||||
:color="child.to === route.path ? 'primary' : 'gray'"
|
||||
:icon="child.icon"
|
||||
class="ml-4"
|
||||
:to="child.to"
|
||||
:target="child.target"
|
||||
>
|
||||
{{ child.label }}
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>-->
|
||||
|
||||
</template>
|
||||
@@ -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>
|
||||
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.tenants.find(i => auth.activeTenant === i.id).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>
|
||||
|
||||
const { isHelpSlideoverOpen } = useDashboard()
|
||||
const { isDashboardSearchModalOpen } = useUIState()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
@@ -7,23 +8,26 @@ const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const items = computed(() => [
|
||||
[{
|
||||
slot: 'account',
|
||||
label: '',
|
||||
disabled: true
|
||||
}], [{
|
||||
}], [/*{
|
||||
label: 'Mein Profil',
|
||||
icon: 'i-heroicons-user',
|
||||
to: `/profiles/show/${profileStore.activeProfile.id}`
|
||||
},*/{
|
||||
label: 'Passwort ändern',
|
||||
icon: 'i-heroicons-shield-check',
|
||||
to: `/password-change`
|
||||
},{
|
||||
label: 'Abmelden',
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle',
|
||||
click: async () => {
|
||||
await supabase.auth.signOut()
|
||||
//await dataStore.clearStore()
|
||||
await router.push('/login')
|
||||
await auth.logout()
|
||||
|
||||
}
|
||||
}]
|
||||
@@ -33,10 +37,10 @@ const items = computed(() => [
|
||||
<template>
|
||||
<UDropdown mode="hover" :items="items" :ui="{ width: 'w-full', item: { disabled: 'cursor-text select-text' } }" :popper="{ strategy: 'absolute', placement: 'top' }" class="w-full">
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" class="w-full" :label="profileStore.activeProfile.fullName" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<template #leading>
|
||||
<UAvatar :alt="profileStore.activeProfile ? profileStore.activeProfile.fullName : ''" size="xs" />
|
||||
</template>
|
||||
<UButton color="gray" variant="ghost" class="w-full" :label="auth.user.email" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<!-- <template #leading>
|
||||
<UAvatar :alt="auth.user.email" size="xs" />
|
||||
</template>-->
|
||||
|
||||
<template #trailing>
|
||||
<UIcon name="i-heroicons-ellipsis-vertical" class="w-5 h-5 ml-auto" />
|
||||
@@ -50,7 +54,7 @@ const items = computed(() => [
|
||||
Angemeldet als
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{profileStore.activeProfile.email}}
|
||||
{{auth.user.email}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@ const props = defineProps({
|
||||
const incomingInvoices = ref({})
|
||||
|
||||
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()
|
||||
|
||||
@@ -7,9 +7,9 @@ let unallocatedStatements = ref(0)
|
||||
let bankaccounts = ref([])
|
||||
|
||||
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
|
||||
bankaccounts.value = await useSupabaseSelect("bankaccounts")
|
||||
bankaccounts.value = await useEntities("bankaccounts").select()
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
@@ -4,21 +4,20 @@ import dayjs from "dayjs";
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
let incomeData = ref({})
|
||||
let expenseData = ref({})
|
||||
|
||||
const setup = async () => {
|
||||
let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",profileStore.currentTenant).eq("state","Gebucht").in('type',['invoices','advanceInvoices','cancellationInvoices'])).data
|
||||
console.log(incomeRawData)
|
||||
//let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",profileStore.currentTenant).eq("state","Gebucht").in('type',['invoices','advanceInvoices','cancellationInvoices'])).data
|
||||
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 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 supabase.from("incominginvoices").select().eq("tenant",profileStore.currentTenant)).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 withoutInvoiceRawDataIncomes = []
|
||||
@@ -87,7 +86,7 @@ const setup = async () => {
|
||||
}
|
||||
Object.keys(expenseMonths).forEach(month => {
|
||||
let dates = Object.keys(expenseData.value).filter(i => i.split("-")[1] === month && i.split("-")[2] === dayjs().format("YY"))
|
||||
console.log(dates)
|
||||
|
||||
|
||||
|
||||
dates.forEach(date => {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const profileStore = useProfileStore();
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
let unpaidInvoicesSum = ref(0)
|
||||
let unpaidInvoicesCount = ref(0)
|
||||
@@ -15,7 +14,7 @@ let draftInvoicesCount = ref(0)
|
||||
let countPreparedOpenIncomingInvoices = ref(0)
|
||||
|
||||
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 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))
|
||||
|
||||
console.log(finalizedDocuments)
|
||||
|
||||
|
||||
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)) {
|
||||
unpaidOverdueInvoicesSum.value += useSum().getCreatedDocumentSum(i, documents) - i.statementallocations.reduce((n,{amount}) => n + amount, 0)
|
||||
unpaidOverdueInvoicesCount.value += 1
|
||||
@@ -45,7 +42,7 @@ const setupPage = async () => {
|
||||
})
|
||||
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>
|
||||
|
||||
const openTasks = ref([])
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
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()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const phasesCounter = ref({})
|
||||
|
||||
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 => {
|
||||
if(project.phases && project.phases.length > 0){
|
||||
|
||||
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,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 {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||
/*const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||
|
||||
return (await axios({
|
||||
method: "POST",
|
||||
@@ -35,7 +35,11 @@ export const useFunctions = () => {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
}
|
||||
})).data.usedNumber
|
||||
})).data.usedNumber*/
|
||||
|
||||
return (await useNuxtApp().$api(`/api/functions/usenextnumber/${numberRange}`,)).usedNumber
|
||||
|
||||
|
||||
}
|
||||
|
||||
const useCreateTicket = async (subject,message,url,source) => {
|
||||
@@ -80,9 +84,17 @@ export const useFunctions = () => {
|
||||
}
|
||||
|
||||
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",
|
||||
url: `${baseURL}/functions/createpdf`,
|
||||
data: {
|
||||
@@ -93,7 +105,7 @@ export const useFunctions = () => {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
}
|
||||
})
|
||||
})*/
|
||||
|
||||
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 }
|
||||
}
|
||||
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}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
|
||||
import MainNav from "~/components/MainNav.vue";
|
||||
import dayjs from "dayjs";
|
||||
import {useProfileStore} from "~/stores/profile.js";
|
||||
import {useCapacitor} from "../composables/useCapacitor.js";
|
||||
import GlobalMessages from "~/components/GlobalMessages.vue";
|
||||
import TenantDropdown from "~/components/TenantDropdown.vue";
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const colorMode = useColorMode()
|
||||
const { isHelpSlideoverOpen } = useDashboard()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||
const auth = useAuthStore()
|
||||
|
||||
//profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||
|
||||
const month = dayjs().format("MM")
|
||||
|
||||
@@ -65,8 +65,8 @@ const actions = [
|
||||
]
|
||||
|
||||
|
||||
const groups = computed(() =>
|
||||
[{
|
||||
const groups = computed(() => [
|
||||
{
|
||||
key: 'actions',
|
||||
commands: actions
|
||||
},{
|
||||
@@ -99,7 +99,8 @@ const groups = computed(() =>
|
||||
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',
|
||||
icon: 'i-heroicons-plus',
|
||||
to: '/settings/members'
|
||||
@@ -112,56 +113,163 @@ const footerLinks = [/*{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardLayout class="safearea" v-if="profileStore.loaded">
|
||||
|
||||
<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' }">
|
||||
<template #left>
|
||||
<ProfileDropdown class="w-full" />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardSidebar id="sidebar">
|
||||
<template #header>
|
||||
<UDashboardSearchButton label="Suche..."/>
|
||||
</template>
|
||||
|
||||
<GlobalMessages/>
|
||||
|
||||
<MainNav/>
|
||||
|
||||
<div class="flex-1" />
|
||||
|
||||
|
||||
<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 v-if="!auth.loading">
|
||||
<div v-if="auth.tenants.find(t => t.id === auth.activeTenant).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>
|
||||
|
||||
</template>
|
||||
</UDashboardSidebar>
|
||||
</UDashboardPanel>
|
||||
<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>
|
||||
|
||||
<UDashboardPage>
|
||||
<UDashboardPanel grow>
|
||||
<slot />
|
||||
|
||||
</UCard>
|
||||
</UContainer>
|
||||
</div>
|
||||
<div v-else-if="auth.tenants.find(t => t.id === auth.activeTenant).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.tenants.find(t => t.id === auth.activeTenant).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>
|
||||
<UDashboardNavbar style="margin-top: env(safe-area-inset-top, 10px) !important;" :class="['!border-transparent']" :ui="{ left: 'flex-1' }">
|
||||
<template #left>
|
||||
<TenantDropdown class="w-full" />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardSidebar id="sidebar">
|
||||
<!-- <template #header>
|
||||
<UDashboardSearchButton label="Suche..."/>
|
||||
</template>-->
|
||||
|
||||
<!--
|
||||
<GlobalMessages/>
|
||||
-->
|
||||
|
||||
<MainNav/>
|
||||
|
||||
<div class="flex-1" />
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
</template>
|
||||
</UDashboardSidebar>
|
||||
</UDashboardPanel>
|
||||
</UDashboardPage>
|
||||
|
||||
<UDashboardPage>
|
||||
<UDashboardPanel grow>
|
||||
<slot />
|
||||
</UDashboardPanel>
|
||||
</UDashboardPage>
|
||||
|
||||
|
||||
|
||||
<!-- ~/components/HelpSlideover.vue -->
|
||||
<HelpSlideover/>
|
||||
<!-- ~/components/NotificationsSlideover.vue -->
|
||||
<NotificationsSlideover />
|
||||
<HelpSlideover/>
|
||||
|
||||
<!--<NotificationsSlideover />
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUDashboardSearch :groups="groups" hide-color-mode/>
|
||||
</ClientOnly>-->
|
||||
</UDashboardLayout>
|
||||
</div>
|
||||
|
||||
<ClientOnly>
|
||||
<LazyUDashboardSearch :groups="groups" hide-color-mode/>
|
||||
</ClientOnly>
|
||||
</UDashboardLayout>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col"
|
||||
@@ -178,12 +286,25 @@ const footerLinks = [/*{
|
||||
class="w-1/3 mx-auto my-10"
|
||||
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 v-else>
|
||||
|
||||
|
||||
<UProgress animation="carousel" class="w-3/4 mx-auto mt-10" />
|
||||
</div>
|
||||
|
||||
@@ -192,7 +313,5 @@ const footerLinks = [/*{
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.testclass {
|
||||
color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
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")
|
||||
}
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
const router = useRouter()
|
||||
|
||||
console.log(await useCapacitor().getIsPhone())
|
||||
|
||||
if(await useCapacitor().getIsPhone()) {
|
||||
return router.push('/mobile')
|
||||
}
|
||||
})
|
||||
@@ -34,7 +34,8 @@ export default defineNuxtConfig({
|
||||
|
||||
supabase: {
|
||||
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo",
|
||||
url: "https://uwppvcxflrcsibuzsbil.supabase.co"
|
||||
url: "https://uwppvcxflrcsibuzsbil.supabase.co",
|
||||
redirect:false
|
||||
},
|
||||
|
||||
vite: {
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
@@ -15,16 +10,14 @@ defineShortcuts({
|
||||
}
|
||||
})
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
const tempStore = useTempStore()
|
||||
const router = useRouter()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const items = ref([])
|
||||
const dataLoaded = ref(false)
|
||||
|
||||
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) => {
|
||||
let renderedAllocationsTemp = await renderedAllocations(i.id)
|
||||
@@ -44,8 +37,8 @@ const setupPage = async () => {
|
||||
|
||||
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 incominginvoices = (await useSupabaseSelect("incominginvoices", "*, vendor(*)")).filter(i => i.accounts.find(x => x.account === account))
|
||||
let statementallocations = (await useEntities("statementallocations").select("*, bs_id(*)")).filter(i => i.account === account)
|
||||
let incominginvoices = (await useEntities("incominginvoices").select("*, vendor(*)")).filter(i => i.accounts.find(x => x.account === account))
|
||||
|
||||
let tempstatementallocations = statementallocations.map(i => {
|
||||
return {
|
||||
|
||||
@@ -3,19 +3,17 @@
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const itemInfo = ref(null)
|
||||
const statementallocations = ref([])
|
||||
const incominginvoices = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
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
|
||||
incominginvoices.value = (await useSupabaseSelect("incominginvoices", "*, vendor(*)")).filter(i => i.accounts.find(x => x.account == route.params.id))
|
||||
itemInfo.value = (await useEntities("accounts").selectSpecial("*")).find(i => i.id === Number(route.params.id))
|
||||
statementallocations.value = (await useEntities("statementallocations").select("*, bs_id(*)")).filter(i => i.account === Number(route.params.id))
|
||||
incominginvoices.value = (await useEntities("incominginvoices").select("*, vendor(*)")).filter(i => i.accounts.find(x => x.account === Number(route.params.id)))
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
@@ -14,17 +10,15 @@ defineShortcuts({
|
||||
}
|
||||
})
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
const tempStore = useTempStore()
|
||||
const router = useRouter()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const bankstatements = ref([])
|
||||
const bankaccounts = ref([])
|
||||
|
||||
const setupPage = async () => {
|
||||
bankstatements.value = (await supabase.from("bankstatements").select("*, statementallocations(*)").is("archived",false).eq('tenant', profileStore.currentTenant).order("date", {ascending:false})).data
|
||||
bankaccounts.value = await useSupabaseSelect("bankaccounts")
|
||||
bankstatements.value = (await useEntities("bankstatements").select("*, statementallocations(*)", "date", false))
|
||||
bankaccounts.value = await useEntities("bankaccounts").select()
|
||||
}
|
||||
|
||||
const templateColumns = [
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import {filter} from "vuedraggable/dist/vuedraggable.common.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
@@ -38,21 +36,22 @@ const ownaccounts = ref([])
|
||||
|
||||
const loading = ref(true)
|
||||
const setup = async () => {
|
||||
loading.value = true
|
||||
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))
|
||||
|
||||
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 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
|
||||
ownaccounts.value = (await supabase.from("ownaccounts").select()).data
|
||||
customers.value = (await supabase.from("customers").select()).data
|
||||
vendors.value = (await supabase.from("vendors").select()).data
|
||||
accounts.value = (await useEntities("accounts").selectSpecial("*","number",true))
|
||||
ownaccounts.value = (await useEntities("ownaccounts").select())
|
||||
customers.value = (await useEntities("customers").select())
|
||||
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 = openDocuments.value.map(i => {
|
||||
@@ -71,15 +70,12 @@ const setup = async () => {
|
||||
allocatedIncomingInvoices.value = incominginvoices.filter(i => i.statementallocations.find(x => x.bs_id === itemInfo.value.id))
|
||||
console.log(allocatedDocuments.value)
|
||||
console.log(allocatedIncomingInvoices.value)
|
||||
//openIncomingInvoices.value = (await useSupabaseSelect("incominginvoices","*, statementallocations(*), vendor(*)")).filter(i => i.statementallocations.length === 0 )
|
||||
openIncomingInvoices.value = (await useSupabaseSelect("incominginvoices","*, statementallocations(*), vendor(*)")).filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== getInvoiceSum(i,false))
|
||||
openIncomingInvoices.value = (await useEntities("incominginvoices").select("*, statementallocations(*), vendor(*)")).filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== getInvoiceSum(i,false))
|
||||
//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)
|
||||
|
||||
|
||||
let allocations = (await supabase.from("createddocuments").select(`*, statementallocations(*)`).eq("id",50)).data
|
||||
|
||||
loading.value = false
|
||||
|
||||
}
|
||||
@@ -151,15 +147,17 @@ const showMoreWithoutRecipe = ref(false)
|
||||
const showMoreText = ref(false)
|
||||
|
||||
const saveAllocation = async (allocation) => {
|
||||
|
||||
//TODO: BACKEND CHANGE SAVE/REMOVE
|
||||
console.log(allocation)
|
||||
|
||||
const {data,error} = await supabase.from("statementallocations").insert({
|
||||
...allocation,
|
||||
tenant: profileStore.currentTenant
|
||||
}).select()
|
||||
const res = await useNuxtApp().$api("/api/banking/statements",{
|
||||
method: "POST",
|
||||
body: {
|
||||
data: allocation
|
||||
}
|
||||
})
|
||||
|
||||
if(data) {
|
||||
if(res) {
|
||||
await setup()
|
||||
accountToSave.value = null
|
||||
vendorAccountToSave.value = null
|
||||
@@ -173,7 +171,9 @@ const saveAllocation = async (allocation) => {
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -356,7 +356,9 @@ const archiveStatement = async () => {
|
||||
<span class="font-semibold">Konto:</span>
|
||||
</td>
|
||||
<td>
|
||||
<!--
|
||||
{{dataStore.getBankAccountById(itemInfo.account) ? dataStore.getBankAccountById(itemInfo.account).name || separateIBAN(dataStore.getBankAccountById(itemInfo.account).iban) : ""}}
|
||||
-->
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr class="flex-row flex justify-between">
|
||||
|
||||
@@ -7,15 +7,12 @@ import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
//TODO BACKEND CHANGE COLOR IN TENANT FOR RENDERING
|
||||
|
||||
//Config
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const mode = ref(route.params.mode || "grid")
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
@@ -130,13 +127,13 @@ const calendarOptionsTimeline = ref({
|
||||
|
||||
const loaded = ref(false)
|
||||
const setupPage = async () => {
|
||||
let tempData = (await useSupabaseSelect("events", "*")).filter(i => !i.archived)
|
||||
let absencerequests = (await useSupabaseSelect("absencerequests", "*, profile(*)")).filter(i => !i.archived)
|
||||
let projects = (await useSupabaseSelect("projects", "*")).filter(i => !i.archived)
|
||||
let inventoryitems = (await useSupabaseSelect("inventoryitems", "*")).filter(i => !i.archived)
|
||||
let inventoryitemgroups = (await useSupabaseSelect("inventoryitemgroups", "*")).filter(i => !i.archived)
|
||||
let profiles = (await useSupabaseSelect("profiles", "*")).filter(i => !i.archived)
|
||||
let vehicles = (await useSupabaseSelect("vehicles", "*")).filter(i => !i.archived)
|
||||
let tempData = (await useEntities("events").select()).filter(i => !i.archived)
|
||||
let absencerequests = (await useEntities("absencerequests").select("*, profile(*)")).filter(i => !i.archived)
|
||||
let projects = (await useEntities("projects").select( "*")).filter(i => !i.archived)
|
||||
let inventoryitems = (await useEntities("inventoryitems").select()).filter(i => !i.archived)
|
||||
let inventoryitemgroups = (await useEntities("inventoryitemgroups").select()).filter(i => !i.archived)
|
||||
let profiles = (await useEntities("profiles").select()).filter(i => !i.archived)
|
||||
let vehicles = (await useEntities("vehicles").select()).filter(i => !i.archived)
|
||||
|
||||
calendarOptionsGrid.value.initialEvents = [
|
||||
...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,7 +1,5 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
import { format, isToday } from 'date-fns'
|
||||
import dayjs from "dayjs"
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
' ': () => {
|
||||
|
||||
@@ -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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,11 +31,11 @@
|
||||
</UDashboardNavbar>
|
||||
<UDashboardToolbar>
|
||||
<template #left>
|
||||
<!-- <UCheckbox
|
||||
v-model="showDrafts"
|
||||
label="Entwürfe Anzeigen"
|
||||
class="my-auto mr-3"
|
||||
/>-->
|
||||
<!-- <UCheckbox
|
||||
v-model="showDrafts"
|
||||
label="Entwürfe Anzeigen"
|
||||
class="my-auto mr-3"
|
||||
/>-->
|
||||
<USelectMenu
|
||||
v-model="selectedTypes"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
@@ -79,7 +79,7 @@
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
{{sort}}
|
||||
<UTabs :items="selectedTypes" class="m-3">
|
||||
<template #item="{item}">
|
||||
<div style="height: 80vh; overflow-y: scroll">
|
||||
@@ -128,8 +128,10 @@
|
||||
</span>
|
||||
</template>
|
||||
<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 #reference-data="{row}">
|
||||
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.documentNumber}}</span>
|
||||
@@ -165,10 +167,6 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
@@ -207,8 +205,9 @@ const router = useRouter()
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
|
||||
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)).filter(i => !i.archived)
|
||||
}
|
||||
|
||||
setupPage()
|
||||
@@ -216,47 +215,38 @@ setupPage()
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "reference",
|
||||
label: "Referenz",
|
||||
sortable: true
|
||||
label: "Referenz"
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
label: "Typ",
|
||||
sortable: true
|
||||
},{
|
||||
label: "Typ"
|
||||
}, {
|
||||
key: 'state',
|
||||
label: "Status",
|
||||
sortable: true
|
||||
label: "Status"
|
||||
},
|
||||
{
|
||||
key: "amount",
|
||||
label: "Betrag",
|
||||
sortable: true
|
||||
label: "Betrag"
|
||||
},
|
||||
{
|
||||
key: 'partner',
|
||||
label: "Kunde",
|
||||
sortable: true
|
||||
label: "Kunde"
|
||||
},
|
||||
{
|
||||
key: "date",
|
||||
label: "Datum",
|
||||
sortable: true
|
||||
label: "Datum"
|
||||
},
|
||||
{
|
||||
key: "amountOpen",
|
||||
label: "Offener Betrag",
|
||||
sortable: true
|
||||
label: "Offener Betrag"
|
||||
},
|
||||
{
|
||||
key: "paid",
|
||||
label: "Bezahlt",
|
||||
sortable: true
|
||||
label: "Bezahlt"
|
||||
},
|
||||
{
|
||||
key: "dueDate",
|
||||
label: "Fällig",
|
||||
sortable: true
|
||||
label: "Fällig"
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(tempStore.columns["createddocuments"] ? tempStore.columns["createddocuments"] : templateColumns)
|
||||
@@ -291,19 +281,19 @@ const types = computed(() => {
|
||||
const selectItem = (item) => {
|
||||
console.log(item)
|
||||
|
||||
if(item.state === "Entwurf"){
|
||||
if (item.state === "Entwurf") {
|
||||
router.push(`/createDocument/edit/${item.id}`)
|
||||
} else if(item.state !== "Entwurf") {
|
||||
} else if (item.state !== "Entwurf") {
|
||||
router.push(`/createDocument/show/${item.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
const displayCurrency = (value, currency = "€") => {
|
||||
return `${Number(value).toFixed(2).replace(".",",")} ${currency}`
|
||||
return `${Number(value).toFixed(2).replace(".", ",")} ${currency}`
|
||||
}
|
||||
|
||||
|
||||
const searchString = ref(tempStore.searchStrings['createddocuments'] ||'')
|
||||
const searchString = ref(tempStore.searchStrings['createddocuments'] || '')
|
||||
|
||||
const clearSearchString = () => {
|
||||
tempStore.clearSearchString('createddocuments')
|
||||
@@ -312,7 +302,7 @@ const clearSearchString = () => {
|
||||
const selectedFilters = ref([])
|
||||
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 temp = 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")
|
||||
|
||||
/*if(showDrafts.value === true) {
|
||||
@@ -321,7 +311,7 @@ const filteredRows = computed(() => {
|
||||
temp = temp.filter(i => i.state !== "Entwurf")
|
||||
}*/
|
||||
|
||||
if(selectedFilters.value.includes("Nur offene anzeigen")){
|
||||
if (selectedFilters.value.includes("Nur offene anzeigen")) {
|
||||
temp = temp.filter(i => !isPaid(i))
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("createddocuments","*, customer(id,name)","documentDate")
|
||||
items.value = await useEntities("createddocuments").select("*, customer(id,name)","documentDate")
|
||||
}
|
||||
|
||||
const searchString = ref("")
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<script setup>
|
||||
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
@@ -11,41 +9,28 @@ defineShortcuts({
|
||||
},
|
||||
})
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const modal = useModal()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const itemInfo = ref({})
|
||||
const linkedDocument =ref({})
|
||||
const currentTenant = ref({})
|
||||
const setupPage = async () => {
|
||||
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(*)")
|
||||
|
||||
console.log(itemInfo.value)
|
||||
|
||||
linkedDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
||||
|
||||
|
||||
|
||||
//const {data,error} = await supabase.from("files").select("id").eq("createddocument", route.params.id).order("id",{ascending:true})
|
||||
//linkedDocument.value = data[data.length -1]
|
||||
|
||||
}
|
||||
|
||||
currentTenant.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
|
||||
console.log(currentTenant.value)
|
||||
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const openEmail = () => {
|
||||
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
|
||||
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(currentTenant.value.standardEmailForInvoices || "")}`)
|
||||
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(auth.activeTenantData.standardEmailForInvoices || "")}`)
|
||||
} else {
|
||||
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]`)
|
||||
}
|
||||
@@ -54,7 +39,7 @@ const openEmail = () => {
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
title="Erstelltes Dokument anzeigen"
|
||||
title="Erstelltes Dokument anzeigen"
|
||||
>
|
||||
|
||||
</UDashboardNavbar>
|
||||
@@ -66,10 +51,10 @@ const openEmail = () => {
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
<!-- <UButton
|
||||
:to="dataStore.documents.find(i => i.createdDocument === itemInfo.id) ? dataStore.documents.find(i => i.createdDocument === itemInfo.id).url : ''"
|
||||
target="_blank"
|
||||
>In neuen Tab anzeigen</UButton>-->
|
||||
<!-- <UButton
|
||||
:to="dataStore.documents.find(i => i.createdDocument === itemInfo.id) ? dataStore.documents.find(i => i.createdDocument === itemInfo.id).url : ''"
|
||||
target="_blank"
|
||||
>In neuen Tab anzeigen</UButton>-->
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-right-end-on-rectangle"
|
||||
@click="modal.open(CopyCreatedDocumentModal, {
|
||||
@@ -81,8 +66,8 @@ const openEmail = () => {
|
||||
Kopieren
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="openEmail"
|
||||
icon="i-heroicons-envelope"
|
||||
@click="openEmail"
|
||||
icon="i-heroicons-envelope"
|
||||
>
|
||||
E-Mail
|
||||
</UButton>
|
||||
@@ -96,16 +81,16 @@ const openEmail = () => {
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="itemInfo.project"
|
||||
@click="router.push(`/standardEntity/projects/show/${itemInfo.project}`)"
|
||||
icon="i-heroicons-link"
|
||||
@click="router.push(`/standardEntity/projects/show/${itemInfo.project}`)"
|
||||
icon="i-heroicons-link"
|
||||
variant="outline"
|
||||
>
|
||||
Projekt
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="itemInfo.customer"
|
||||
@click="router.push(`/standardEntity/customers/show/${itemInfo.customer}`)"
|
||||
icon="i-heroicons-link"
|
||||
@click="router.push(`/standardEntity/customers/show/${itemInfo.customer}`)"
|
||||
icon="i-heroicons-link"
|
||||
variant="outline"
|
||||
>
|
||||
Kunde
|
||||
|
||||
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,12 @@
|
||||
<script setup>
|
||||
|
||||
//TODO: BACKENDCHANGE EMAIL SENDING
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const emailData = ref({
|
||||
to:"",
|
||||
@@ -23,14 +24,14 @@ const loadedDocuments = ref([])
|
||||
const loaded = ref(false)
|
||||
const noAccountsPresent = ref(false)
|
||||
const setupPage = async () => {
|
||||
emailAccounts.value = await useSupabaseSelect("emailAccounts")
|
||||
emailAccounts.value = await useEntities("emailAccounts").select()
|
||||
|
||||
if(emailAccounts.value.length === 0) {
|
||||
noAccountsPresent.value = true
|
||||
} else {
|
||||
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
|
||||
if(route.query.to) emailData.value.to = route.query.to
|
||||
@@ -44,15 +45,13 @@ const setupPage = async () => {
|
||||
const data = await useFiles().selectSomeDocuments(JSON.parse(route.query.loadDocuments))
|
||||
console.log(data)
|
||||
|
||||
|
||||
|
||||
if(data) loadedDocuments.value = data
|
||||
|
||||
//console.log(loadedDocuments.value)
|
||||
|
||||
if(loadedDocuments.value.length > 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) {
|
||||
console.log("Contact")
|
||||
@@ -63,7 +62,6 @@ const setupPage = async () => {
|
||||
emailData.value.to = loadedDocuments.value[0].createddocument.customer.infoData.email
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
loaded.value = true
|
||||
@@ -132,25 +130,35 @@ const sendEmail = async () => {
|
||||
|
||||
for await (const doc of loadedDocuments.value) {
|
||||
|
||||
const {data,error} = await supabase.storage.from("filesdev").download(doc.path)
|
||||
//const {data,error} = await supabase.storage.from("filesdev").download(doc.path)
|
||||
|
||||
const res = await useFiles().downloadFile(doc.id, null, true)
|
||||
|
||||
body.attachments.push({
|
||||
filename: doc.path.split("/")[doc.path.split("/").length -1],
|
||||
content: await blobToBase64(data),
|
||||
contentType: data.type,
|
||||
content: await blobToBase64(res),
|
||||
contentType: res.type,
|
||||
encoding: "base64",
|
||||
contentDisposition: "attachment"
|
||||
})
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.functions.invoke('send_email', {
|
||||
body
|
||||
const res = await useNuxtApp().$api("/api/emailasuser/send",{
|
||||
method: "POST",
|
||||
body: body,
|
||||
})
|
||||
if(error) {
|
||||
|
||||
console.log(res)
|
||||
|
||||
|
||||
/*const { data, error } = await supabase.functions.invoke('send_email', {
|
||||
body
|
||||
})*/
|
||||
if(!res.success) {
|
||||
toast.add({title: "Fehler beim Absenden der E-Mail", color: "rose"})
|
||||
|
||||
} else if(data) {
|
||||
router.push("/")
|
||||
} else {
|
||||
navigateTo("/")
|
||||
toast.add({title: "E-Mail zum Senden eingereiht"})
|
||||
}
|
||||
|
||||
|
||||
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 {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
||||
import DocumentUploadModal from "~/components/DocumentUploadModal.vue";
|
||||
import dayjs from "dayjs";
|
||||
import arraySort from "array-sort";
|
||||
import {useTempStore} from "~/stores/temp.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
/*'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},*/
|
||||
'+': () => {
|
||||
//Hochladen
|
||||
uploadModalOpen.value = true
|
||||
@@ -30,9 +21,10 @@ defineShortcuts({
|
||||
|
||||
if(entry.type === "file") {
|
||||
showFile(entry.id)
|
||||
console.log(entry)
|
||||
} else {
|
||||
} else if(createFolderModalOpen.value === false && entry.type === "folder") {
|
||||
changeFolder(currentFolders.value.find(i => i.id === entry.id))
|
||||
} else if(createFolderModalOpen.value === true) {
|
||||
createFolder()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,13 +47,11 @@ defineShortcuts({
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const tempStore = useTempStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const modal = useModal()
|
||||
|
||||
dataStore.fetchDocuments()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const uploadModalOpen = ref(false)
|
||||
const createFolderModalOpen = ref(false)
|
||||
@@ -69,7 +59,7 @@ const uploadInProgress = ref(false)
|
||||
const fileUploadFormData = ref({
|
||||
tags: ["Eingang"],
|
||||
path: "",
|
||||
tenant: profileStore.currentTenant,
|
||||
tenant: auth.activeTenant,
|
||||
folder: null
|
||||
})
|
||||
|
||||
@@ -92,15 +82,16 @@ const isDragTarget = ref(false)
|
||||
const loaded = ref(false)
|
||||
|
||||
const setupPage = async () => {
|
||||
folders.value = await useSupabaseSelect("folders")
|
||||
folders.value = await useEntities("folders").select()
|
||||
|
||||
documents.value = await files.selectDocuments()
|
||||
|
||||
filetags.value = await useSupabaseSelect("filetags")
|
||||
filetags.value = await useEntities("filetags").select()
|
||||
|
||||
if(route.query) {
|
||||
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) {
|
||||
console.log("files dropped")
|
||||
event.preventDefault()
|
||||
|
||||
}
|
||||
@@ -213,14 +203,10 @@ const changeFolder = async (newFolder) => {
|
||||
|
||||
const createFolderData = ref({})
|
||||
const createFolder = async () => {
|
||||
const {data,error} = await supabase
|
||||
.from("folders")
|
||||
.insert({
|
||||
tenant: profileStore.currentTenant,
|
||||
parent: currentFolder.value ? currentFolder.value.id : undefined,
|
||||
name: createFolderData.value.name,
|
||||
|
||||
})
|
||||
const res = await useEntities("folders").create({
|
||||
parent: currentFolder.value ? currentFolder.value.id : undefined,
|
||||
name: createFolderData.value.name,
|
||||
})
|
||||
|
||||
createFolderModalOpen.value = false
|
||||
|
||||
@@ -229,61 +215,14 @@ const createFolder = async () => {
|
||||
}
|
||||
|
||||
const downloadSelected = async () => {
|
||||
const bucket = "filesdev";
|
||||
|
||||
let files = []
|
||||
|
||||
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"] ||'')
|
||||
@@ -295,7 +234,6 @@ const renderedFileList = computed(() => {
|
||||
type: "file"
|
||||
}
|
||||
})
|
||||
console.log(currentFolders.value)
|
||||
|
||||
arraySort(files, (a,b) => {
|
||||
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 showFile = (fileId) => {
|
||||
console.log(fileId)
|
||||
modal.open(DocumentDisplayModal,{
|
||||
documentData: documents.value.find(i => i.id === fileId),
|
||||
onUpdatedNeeded: setupPage()
|
||||
@@ -413,7 +350,10 @@ const clearSearchString = () => {
|
||||
</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
|
||||
@click="createFolderModalOpen = true"
|
||||
variant="outline"
|
||||
|
||||
@@ -4,9 +4,7 @@ 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()
|
||||
|
||||
@@ -4,16 +4,10 @@ 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,
|
||||
@@ -36,14 +30,18 @@ const itemInfo = ref({
|
||||
})
|
||||
|
||||
const costcentres = ref([])
|
||||
const vendors = ref([])
|
||||
const accounts = ref([])
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -65,7 +63,7 @@ const loadFile = async (id) => {
|
||||
const changeNetMode = (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 +137,7 @@ const updateIncomingInvoice = async (setBooked = false) => {
|
||||
} else {
|
||||
item.state = "Entwurf"
|
||||
}
|
||||
const data = await dataStore.updateItem('incominginvoices',item)
|
||||
const data = await useEntities('incominginvoices').update(itemInfo.value.id,item)
|
||||
}
|
||||
|
||||
const findIncomingInvoiceErrors = computed(() => {
|
||||
@@ -172,6 +170,19 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
<template>
|
||||
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@confirmed="useEntities('incominginvoices').archive(route.params.id)"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie den Eingangsbeleg wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
<UButton
|
||||
@click="updateIncomingInvoice(false)"
|
||||
>
|
||||
@@ -234,7 +245,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
<InputGroup>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.vendor"
|
||||
:options="dataStore.vendors"
|
||||
:options="vendors"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
@@ -247,7 +258,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
{{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'}}
|
||||
{{vendors.find(vendor => vendor.id === itemInfo.vendor) ? vendors.find(vendor => vendor.id === itemInfo.vendor).name : 'Lieferant auswählen'}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<EntityModalButtons
|
||||
@@ -256,10 +267,10 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
@return-data="(data) => itemInfo.vendor = data.id"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-heroicons-x-mark"
|
||||
variant="outline"
|
||||
color="rose"
|
||||
@click="itemInfo.vendor = null"
|
||||
icon="i-heroicons-x-mark"
|
||||
variant="outline"
|
||||
color="rose"
|
||||
@click="itemInfo.vendor = null"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
@@ -323,26 +334,26 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
|
||||
<InputGroup class="my-3">
|
||||
<UButton
|
||||
:variant="!useNetMode ? 'solid' : 'outline'"
|
||||
@click="changeNetMode(false)"
|
||||
:variant="!useNetMode ? 'solid' : 'outline'"
|
||||
@click="changeNetMode(false)"
|
||||
>
|
||||
Brutto
|
||||
</UButton>
|
||||
<UButton
|
||||
:variant="useNetMode ? 'solid' : 'outline'"
|
||||
@click="changeNetMode(true)"
|
||||
: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-->
|
||||
<!-- 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">
|
||||
@@ -369,7 +380,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
class="mb-3"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.accounts"
|
||||
:options="accounts"
|
||||
option-attribute="label"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
@@ -379,7 +390,7 @@ const findIncomingInvoiceErrors = computed(() => {
|
||||
:color="!item.account ? 'rose' : 'primary'"
|
||||
>
|
||||
<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>
|
||||
|
||||
</USelectMenu>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs"
|
||||
import {useSum} from "~/composables/useSum.js";
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
@@ -44,9 +42,14 @@ const sum = useSum()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
const sort = ref({
|
||||
column: 'date',
|
||||
direction: 'desc'
|
||||
})
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("incominginvoices","*, vendor(id,name), statementallocations(id,amount)","created_at",false)
|
||||
//items.value = await useSupabaseSelect("incominginvoices","*, vendor(id,name), statementallocations(id,amount)","created_at",false)
|
||||
items.value = await useEntities("incominginvoices").select("*, vendor(id,name), statementallocations(id,amount)",sort.value.column,sort.value.direction === "asc")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
@@ -54,26 +57,29 @@ setupPage()
|
||||
const templateColumns = [
|
||||
{
|
||||
key: 'reference',
|
||||
label: "Referenz:"
|
||||
label: "Referenz:",
|
||||
sortable: true,
|
||||
}, {
|
||||
key: 'state',
|
||||
label: "Status:"
|
||||
},
|
||||
{
|
||||
key: "date",
|
||||
label: "Datum"
|
||||
label: "Datum",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "vendor",
|
||||
label: "Lieferant"
|
||||
label: "Lieferant",
|
||||
},
|
||||
{
|
||||
key: "amount",
|
||||
label: "Betrag"
|
||||
label: "Betrag",
|
||||
},
|
||||
{
|
||||
key: "dueDate",
|
||||
label: "Fälligkeitsdatum"
|
||||
label: "Fälligkeitsdatum",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "paid",
|
||||
@@ -81,7 +87,8 @@ const templateColumns = [
|
||||
},
|
||||
{
|
||||
key: "paymentType",
|
||||
label: "Zahlart"
|
||||
label: "Zahlart",
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: "description",
|
||||
@@ -92,6 +99,7 @@ const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
|
||||
|
||||
const searchString = ref(tempStore.searchStrings['incominginvoices'] ||'')
|
||||
|
||||
const clearSearchString = () => {
|
||||
@@ -185,6 +193,9 @@ const selectIncomingInvoice = (invoice) => {
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<UTable
|
||||
v-model:sort="sort"
|
||||
sort-mode="manual"
|
||||
@update:sort="setupPage"
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Home">
|
||||
<template #right>
|
||||
<UTooltip text="Notifications" :shortcuts="['N']">
|
||||
<!-- <UTooltip text="Notifications" :shortcuts="['N']">
|
||||
<UButton color="gray" variant="ghost" square @click="isNotificationsSlideoverOpen = true">
|
||||
<UChip :show="unreadMessages" color="primary" inset>
|
||||
<UIcon name="i-heroicons-bell" class="w-5 h-5" />
|
||||
</UChip>
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</UTooltip>-->
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
@@ -23,13 +23,11 @@
|
||||
<UPageGrid>
|
||||
<UDashboardCard
|
||||
title="Buchhaltung"
|
||||
v-if="profileStore.ownTenant.features.accounting"
|
||||
>
|
||||
<display-open-balances/>
|
||||
</UDashboardCard>
|
||||
<UDashboardCard
|
||||
title="Bank"
|
||||
v-if="profileStore.ownTenant.features.accounting"
|
||||
>
|
||||
<display-bankaccounts/>
|
||||
</UDashboardCard>
|
||||
@@ -38,7 +36,7 @@
|
||||
>
|
||||
<display-projects-in-phases/>
|
||||
</UDashboardCard>
|
||||
<UDashboardCard
|
||||
<!--<UDashboardCard
|
||||
title="Anwesende"
|
||||
>
|
||||
<display-present-profiles/>
|
||||
@@ -52,7 +50,7 @@
|
||||
title="Anwesenheiten"
|
||||
>
|
||||
<display-running-working-time/>
|
||||
</UDashboardCard>
|
||||
</UDashboardCard>-->
|
||||
<UDashboardCard
|
||||
title="Aufgaben"
|
||||
>
|
||||
@@ -66,9 +64,7 @@
|
||||
|
||||
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: ["auth","redirect-to-mobile-index"]
|
||||
})
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
@@ -76,24 +72,14 @@ const toast = useToast()
|
||||
const router = useRouter()
|
||||
|
||||
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 () => {
|
||||
unreadMessages.value = (await supabase.from("notifications").select("id,read").eq("read",false)).data.length > 0
|
||||
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
@@ -63,9 +63,7 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
|
||||
136
pages/login.vue
136
pages/login.vue
@@ -1,118 +1,32 @@
|
||||
<script setup >
|
||||
|
||||
import {useProfileStore} from "~/stores/profile.js";
|
||||
import {useCapacitor} from "~/composables/useCapacitor.js";
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: "notLoggedIn"
|
||||
})
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
const router = useRouter()
|
||||
const colorMode = useColorMode()
|
||||
const auth = useAuthStore()
|
||||
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"})
|
||||
}
|
||||
|
||||
} else {
|
||||
//console.log("Login Successful")
|
||||
profileStore.initializeData(user.id)
|
||||
|
||||
if(await useCapacitor().getIsPhone()) {
|
||||
router.push("/mobile")
|
||||
} else {
|
||||
router.push("/")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const doLogin = async (data:any) => {
|
||||
try {
|
||||
await auth.login(data.email, data.password)
|
||||
// Weiterleiten nach erfolgreichem Login
|
||||
toast.add({title:"Einloggen erfolgreich"})
|
||||
return navigateTo("/")
|
||||
} catch (err: any) {
|
||||
toast.add({title:"Zugangsdaten falsch. Bitte überprüfen Sie Ihre Eingaben",color:"rose"})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<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">
|
||||
|
||||
<UColorModeImage
|
||||
light="/Logo.png"
|
||||
dark="/Logo_Dark.png"
|
||||
light="/Logo.png"
|
||||
dark="/Logo_Dark.png"
|
||||
/>
|
||||
|
||||
<UAuthForm
|
||||
@@ -131,29 +45,13 @@ const onSubmit = async (data) => {
|
||||
placeholder: 'Dein Passwort'
|
||||
}]"
|
||||
:loading="false"
|
||||
@submit="onSubmit"
|
||||
:providers="[{label: 'MS365',icon: 'i-simple-icons-microsoft',color: 'gray',click: authenticateWithAzure}]"
|
||||
@submit="doLogin"
|
||||
:submit-button="{label: 'Weiter'}"
|
||||
divider="oder"
|
||||
>
|
||||
|
||||
<template #password-hint>
|
||||
<NuxtLink to="/password-reset" class="text-primary font-medium">Passwort vergessen?</NuxtLink>
|
||||
</template>
|
||||
</UAuthForm>
|
||||
</UCard>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#loginSite {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
#loginForm {
|
||||
width: 30vw;
|
||||
height: 30vh;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
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 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 = [
|
||||
{
|
||||
key: 'employeeNumber',
|
||||
key: 'employee_number',
|
||||
label: "MA-Nummer",
|
||||
},{
|
||||
key: 'fullName',
|
||||
key: 'full_name',
|
||||
label: "Name",
|
||||
},{
|
||||
key: "email",
|
||||
@@ -26,6 +34,7 @@
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="router.push(`/profiles/create`)"
|
||||
disabled
|
||||
>
|
||||
+ Mitarbeiter
|
||||
</UButton>
|
||||
@@ -33,8 +42,7 @@
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UTable
|
||||
:rows="profileStore.profiles"
|
||||
@select="(item) => router.push(`/profiles/show/${item.id}`)"
|
||||
:rows="items"
|
||||
:columns="columns"
|
||||
>
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
@@ -23,8 +21,6 @@ defineShortcuts({
|
||||
})
|
||||
|
||||
const openTab = ref(0)
|
||||
const dataStore = useDataStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
@@ -55,9 +51,9 @@ const setKeys = () => {
|
||||
const setupPage = async() => {
|
||||
|
||||
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") {
|
||||
itemInfo.value = await useSupabaseSelectSingle("projecttypes",route.params.id,"*")
|
||||
itemInfo.value = await useEntities("projecttypes").selectSingle(route.params.id,"*")
|
||||
}
|
||||
|
||||
if(mode.value === "create") {
|
||||
@@ -76,7 +72,7 @@ setupPage()
|
||||
|
||||
const addPhase = () => {
|
||||
itemInfo.value.initialPhases.push({label: '', icon: ''}),
|
||||
setKeys
|
||||
setKeys
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -101,13 +97,13 @@ const addPhase = () => {
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('projecttypes',itemInfo,oldItemInfo)"
|
||||
@click="useEntities('projecttypes').update(itemInfo.id, itemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('projecttypes', itemInfo)"
|
||||
@click="useEntities('projecttypes').create( itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
@@ -160,7 +156,7 @@ const addPhase = () => {
|
||||
variant="outline"
|
||||
class="mb-5"
|
||||
v-if="mode === 'edit'"
|
||||
description="Achtung Änderungen an diesem Projekttypen betreffen nur Projekte die damit neu erstellt werden. Bestehende Projekte bleiben unverändert."
|
||||
description="Achtung Änderungen an diesem Projekttypen betreffen nur Projekte die damit neu erstellt werden. Bestehende Projekte bleiben unverändert."
|
||||
/>
|
||||
|
||||
<UFormGroup
|
||||
@@ -244,9 +240,9 @@ const addPhase = () => {
|
||||
selectedKeyForQuickAction = phase.key"
|
||||
>+ Schnellaktion</UButton>
|
||||
<UButton
|
||||
@click="phase.quickactions = phase.quickactions.filter(i => i.label !== button.label)"
|
||||
v-for="button in phase.quickactions"
|
||||
class="ml-1"
|
||||
@click="phase.quickactions = phase.quickactions.filter(i => i.label !== button.label)"
|
||||
v-for="button in phase.quickactions"
|
||||
class="ml-1"
|
||||
>
|
||||
{{ button.label }}
|
||||
</UButton>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
@@ -19,14 +17,14 @@ defineShortcuts({
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
if (selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
if (selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
@@ -36,14 +34,14 @@ defineShortcuts({
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const tempStore = useTempStore()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
|
||||
const setup = async () => {
|
||||
items.value = await useSupabaseSelect("projecttypes","*")
|
||||
items.value = await useEntities("projecttypes").select()
|
||||
}
|
||||
|
||||
setup()
|
||||
@@ -58,7 +56,7 @@ const templateColumns = [
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
const searchString = ref("")
|
||||
const searchString = ref(tempStore.searchStrings["projecttypes"] || '')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
return useListFilter(searchString.value, items.value)
|
||||
@@ -77,9 +75,10 @@ const filteredRows = computed(() => {
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
@change="tempStore.modifySearchString('projecttypes',searchString)"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
<UKbd value="/"/>
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
@@ -96,8 +95,8 @@ const filteredRows = computed(() => {
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Projekttypen 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>
|
||||
<span class="text-primary-500 font-bold" v-if="row === filteredRows[selectedItem]">{{ row.name }}</span>
|
||||
<span v-else>{{ row.name }}</span>
|
||||
</template>
|
||||
|
||||
</UTable>
|
||||
|
||||
@@ -4,9 +4,7 @@ import DocumentList from "~/components/DocumentList.vue";
|
||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
const items = ref([])
|
||||
const setup = async () => {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
@@ -48,9 +48,7 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
const resources = {
|
||||
customers: {
|
||||
@@ -45,16 +37,19 @@ const resources = {
|
||||
}
|
||||
}
|
||||
|
||||
const numberRanges = ref(profileStore.ownTenant.numberRanges)
|
||||
const numberRanges = ref(auth.activeTenantData.numberRanges)
|
||||
|
||||
const updateNumberRanges = async (range) => {
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("tenants")
|
||||
.update({numberRanges: numberRanges.value})
|
||||
.eq('id',profileStore.currentTenant)
|
||||
const res = await useNuxtApp().$api(`/api/tenant/numberrange/${range}`,{
|
||||
method: "PUT",
|
||||
body: {
|
||||
numberRange: numberRanges.value[range]
|
||||
}
|
||||
})
|
||||
|
||||
console.log(res)
|
||||
|
||||
await profileStore.fetchOwnTenant()
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +57,7 @@ const updateNumberRanges = async (range) => {
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
title="Nummernkreise bearbeiten"
|
||||
title="Nummernkreise bearbeiten"
|
||||
>
|
||||
|
||||
</UDashboardNavbar>
|
||||
@@ -76,7 +71,7 @@ const updateNumberRanges = async (range) => {
|
||||
</UDashboardToolbar>
|
||||
|
||||
<table
|
||||
class="m-3"
|
||||
class="m-3"
|
||||
>
|
||||
<tr class="text-left">
|
||||
<th>Typ</th>
|
||||
@@ -85,19 +80,19 @@ const updateNumberRanges = async (range) => {
|
||||
<th>Suffix</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="key in Object.keys(resources)"
|
||||
v-for="key in Object.keys(resources)"
|
||||
>
|
||||
<td>{{resources[key].label}}</td>
|
||||
<td>
|
||||
<UInput
|
||||
v-model="numberRanges[key].prefix"
|
||||
@change="updateNumberRanges"
|
||||
v-model="numberRanges[key].prefix"
|
||||
@change="updateNumberRanges(key)"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<UInput
|
||||
v-model="numberRanges[key].nextNumber"
|
||||
@change="updateNumberRanges"
|
||||
@change="updateNumberRanges(key)"
|
||||
type="number"
|
||||
step="1"
|
||||
/>
|
||||
@@ -105,7 +100,7 @@ const updateNumberRanges = async (range) => {
|
||||
<td>
|
||||
<UInput
|
||||
v-model="numberRanges[key].suffix"
|
||||
@change="updateNumberRanges"
|
||||
@change="updateNumberRanges(key)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -114,4 +109,4 @@ const updateNumberRanges = async (range) => {
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const itemInfo = ref({
|
||||
features: {},
|
||||
@@ -11,20 +9,21 @@ const itemInfo = ref({
|
||||
})
|
||||
|
||||
const setupPage = async () => {
|
||||
itemInfo.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
|
||||
itemInfo.value = auth.activeTenantData
|
||||
console.log(itemInfo.value)
|
||||
}
|
||||
|
||||
const features = ref(profileStore.ownTenant.features)
|
||||
const businessInfo = ref(profileStore.ownTenant.businessInfo)
|
||||
const features = ref(auth.activeTenantData.features)
|
||||
const businessInfo = ref(auth.activeTenantData.businessInfo)
|
||||
|
||||
const updateTenant = async (newData) => {
|
||||
const {data,error} = await supabase.from("tenants")
|
||||
.update(newData)
|
||||
.eq("id",profileStore.currentTenant)
|
||||
.select()
|
||||
|
||||
if (error) console.log(error)
|
||||
const res = await useNuxtApp().$api(`/api/tenant/other/${auth.activeTenant}`, {
|
||||
method: "PUT",
|
||||
body: {
|
||||
data: newData,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setupPage()
|
||||
@@ -36,7 +35,7 @@ setupPage()
|
||||
</UDashboardNavbar>
|
||||
<UTabs
|
||||
class="p-5"
|
||||
:items="[
|
||||
:items="[
|
||||
{
|
||||
label: 'Dokubox'
|
||||
},{
|
||||
@@ -167,4 +166,4 @@ setupPage()
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
<script setup>
|
||||
const dataStore = useDataStore()
|
||||
const dataStore = useDataStore()
|
||||
|
||||
defineShortcuts({
|
||||
'+': () => {
|
||||
editTemplateModalOpen.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const editTemplateModalOpen = ref(false)
|
||||
const itemInfo = ref({})
|
||||
const texttemplates = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
texttemplates.value = (await useSupabaseSelect("texttemplates")).filter(i => !i.archived)
|
||||
defineShortcuts({
|
||||
'+': () => {
|
||||
editTemplateModalOpen.value = true
|
||||
}
|
||||
})
|
||||
|
||||
setup()
|
||||
const editTemplateModalOpen = ref(false)
|
||||
const itemInfo = ref({})
|
||||
const texttemplates = ref([])
|
||||
const loading = ref(true)
|
||||
|
||||
const expand = ref({
|
||||
openedRows: [],
|
||||
row: {}
|
||||
})
|
||||
const setup = async () => {
|
||||
texttemplates.value = (await useEntities("texttemplates").select()).filter(i => !i.archived)
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const expand = ref({
|
||||
openedRows: [],
|
||||
row: {}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
title="Text Vorlagen"
|
||||
title="Text Vorlagen"
|
||||
>
|
||||
<template #right>
|
||||
<UButton
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="editTemplateModalOpen = true, itemInfo = {}"
|
||||
>
|
||||
+ Erstellen
|
||||
</UButton>
|
||||
</template>
|
||||
>
|
||||
+ Erstellen
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<UCard class="mx-5">
|
||||
@@ -77,9 +79,11 @@
|
||||
|
||||
<UTable
|
||||
class="mt-3"
|
||||
:rows="texttemplates"
|
||||
v-model:expand="expand"
|
||||
:columns="[{key:'name',label:'Name'},{key:'documentType',label:'Dokumententyp'},{key:'default',label:'Standard'},{key:'pos',label:'Position'}]"
|
||||
:rows="texttemplates"
|
||||
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
|
||||
:loading="loading"
|
||||
v-model:expand="expand" :empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Textvorlagen anzuzeigen' }"
|
||||
:columns="[{key:'name',label:'Name'},{key:'documentType',label:'Dokumententyp'},{key:'default',label:'Standard'},{key:'pos',label:'Position'}]"
|
||||
>
|
||||
<template #documentType-data="{row}">
|
||||
{{dataStore.documentTypesForCreation[row.documentType].label}}
|
||||
@@ -121,26 +125,26 @@
|
||||
</UTable>
|
||||
|
||||
|
||||
<!-- <div class="w-3/4 mx-auto mt-5">
|
||||
<UCard
|
||||
v-for="template in dataStore.texttemplates"
|
||||
class="mb-3"
|
||||
>
|
||||
<p class="text-2xl">{{dataStore.documentTypesForCreation[template.documentType].label}}</p>
|
||||
<p class="text-xl">{{template.pos === 'startText' ? 'Einleitung' : 'Ende'}}</p>
|
||||
<p class="text-justify">{{template.text}}</p>
|
||||
<UButton
|
||||
@click="itemInfo = template;
|
||||
editTemplateModalOpen = true"
|
||||
icon="i-heroicons-pencil-solid"
|
||||
variant="outline"
|
||||
/>
|
||||
</UCard>
|
||||
</div>-->
|
||||
<!-- <div class="w-3/4 mx-auto mt-5">
|
||||
<UCard
|
||||
v-for="template in dataStore.texttemplates"
|
||||
class="mb-3"
|
||||
>
|
||||
<p class="text-2xl">{{dataStore.documentTypesForCreation[template.documentType].label}}</p>
|
||||
<p class="text-xl">{{template.pos === 'startText' ? 'Einleitung' : 'Ende'}}</p>
|
||||
<p class="text-justify">{{template.text}}</p>
|
||||
<UButton
|
||||
@click="itemInfo = template;
|
||||
editTemplateModalOpen = true"
|
||||
icon="i-heroicons-pencil-solid"
|
||||
variant="outline"
|
||||
/>
|
||||
</UCard>
|
||||
</div>-->
|
||||
</UDashboardPanelContent>
|
||||
|
||||
<UModal
|
||||
v-model="editTemplateModalOpen"
|
||||
v-model="editTemplateModalOpen"
|
||||
>
|
||||
<UCard class="h-full">
|
||||
<template #header>
|
||||
@@ -157,51 +161,51 @@
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Dokumententyp:"
|
||||
label="Dokumententyp:"
|
||||
>
|
||||
|
||||
<USelectMenu
|
||||
:options="Object.keys(dataStore.documentTypesForCreation).filter(i => i !== 'serialInvoices').map(i => {
|
||||
:options="Object.keys(dataStore.documentTypesForCreation).filter(i => i !== 'serialInvoices').map(i => {
|
||||
return {
|
||||
label: dataStore.documentTypesForCreation[i].label,
|
||||
key: i
|
||||
}
|
||||
})"
|
||||
option-attribute="label"
|
||||
value-attribute="key"
|
||||
v-model="itemInfo.documentType"
|
||||
option-attribute="label"
|
||||
value-attribute="key"
|
||||
v-model="itemInfo.documentType"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Position:"
|
||||
label="Position:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="[{label:'Einleitung',key: 'startText'},{label:'Ende',key: 'endText'}]"
|
||||
option-attribute="label"
|
||||
value-attribute="key"
|
||||
v-model="itemInfo.pos"
|
||||
:options="[{label:'Einleitung',key: 'startText'},{label:'Ende',key: 'endText'}]"
|
||||
option-attribute="label"
|
||||
value-attribute="key"
|
||||
v-model="itemInfo.pos"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Text:"
|
||||
label="Text:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.text"
|
||||
v-model="itemInfo.text"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
|
||||
|
||||
<!-- TODO: Update und Create -->
|
||||
<template #footer>
|
||||
<UButton
|
||||
@click="dataStore.createNewItem('texttemplates',itemInfo);
|
||||
@click="dataStore.createNewItem('texttemplates',itemInfo);
|
||||
editTemplateModalOpen = false"
|
||||
v-if="!itemInfo.id"
|
||||
v-if="!itemInfo.id"
|
||||
>Erstellen</UButton>
|
||||
<UButton
|
||||
@click="dataStore.updateItem('texttemplates',itemInfo);
|
||||
@click="dataStore.updateItem('texttemplates',itemInfo);
|
||||
editTemplateModalOpen = false"
|
||||
v-if="itemInfo.id"
|
||||
v-if="itemInfo.id"
|
||||
>Speichern</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
@@ -3,12 +3,12 @@ import {setPageLayout} from "#app";
|
||||
import {useCapacitor} from "~/composables/useCapacitor.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth",
|
||||
layout: "default",
|
||||
})
|
||||
const route = useRoute()
|
||||
const dataStore = useDataStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const api = useNuxtApp().$api
|
||||
|
||||
|
||||
const type = route.params.type
|
||||
const platform = await useCapacitor().getIsPhone() ? "mobile" : "default"
|
||||
@@ -22,31 +22,30 @@ const mode = ref("list")
|
||||
const items = ref([])
|
||||
const item = ref({})
|
||||
|
||||
const setupPage = async () => {
|
||||
|
||||
if(await useCapacitor().getIsPhone()) {
|
||||
const setupPage = async (sort_column = null, sort_direction = null) => {
|
||||
loaded.value = false
|
||||
|
||||
if (await useCapacitor().getIsPhone()) {
|
||||
setPageLayout("mobile")
|
||||
}
|
||||
|
||||
if(route.params.mode) mode.value = route.params.mode
|
||||
if (route.params.mode) mode.value = route.params.mode
|
||||
|
||||
if(mode.value === "show") {
|
||||
if (mode.value === "show") {
|
||||
//Load Data for Show
|
||||
item.value = await useSupabaseSelectSingle(type, route.params.id, dataType.supabaseSelectWithInformation || "*")
|
||||
} else if(mode.value === "edit") {
|
||||
item.value = await useEntities(type).selectSingle(route.params.id, "*", true)
|
||||
} else if (mode.value === "edit") {
|
||||
//Load Data for Edit
|
||||
const data = JSON.stringify((await supabase.from(type).select().eq("id", route.params.id).single()).data)
|
||||
//await useSupabaseSelectSingle(type, route.params.id)
|
||||
item.value = data
|
||||
item.value = JSON.stringify(await useEntities(type).selectSingle(route.params.id))
|
||||
|
||||
} else if(mode.value === "create") {
|
||||
} else if (mode.value === "create") {
|
||||
//Load Data for Create
|
||||
item.value = JSON.stringify({})
|
||||
|
||||
console.log(item.value)
|
||||
} else if(mode.value === "list") {
|
||||
} else if (mode.value === "list") {
|
||||
//Load Data for List
|
||||
items.value = await useSupabaseSelect(type, dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn,dataType.supabaseSortAscending || false, true)
|
||||
items.value = await useEntities(type).select(dataType.supabaseSelectWithInformation, sort_column || dataType.supabaseSortColumn, sort_direction === "asc")
|
||||
}
|
||||
|
||||
loaded.value = true
|
||||
@@ -66,17 +65,19 @@ setupPage()
|
||||
:platform="platform"
|
||||
/>
|
||||
<EntityEdit
|
||||
v-else-if="loaded && (mode === 'edit' || mode === 'create')"
|
||||
v-else-if="(mode === 'edit' || mode === 'create')"
|
||||
:type="route.params.type"
|
||||
:item="item"
|
||||
:mode="mode"
|
||||
:platform="platform"
|
||||
/>
|
||||
<EntityList
|
||||
v-else-if="loaded && mode === 'list'"
|
||||
:loading="!loaded"
|
||||
v-else-if="mode === 'list'"
|
||||
:type="type"
|
||||
:items="items"
|
||||
:platform="platform"
|
||||
@sort="(i) => setupPage(i.sort_column, i.sort_direction)"
|
||||
/>
|
||||
<UProgress
|
||||
v-else
|
||||
|
||||
15
pages/test.vue
Normal file
15
pages/test.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
async function handleSingle() {
|
||||
await useFiles().downloadFile("f60e8466-7136-4492-ad94-a60603bc3c38") // Einzel-Download
|
||||
}
|
||||
|
||||
async function handleMulti() {
|
||||
await useFiles().downloadFile(undefined, ["f60e8466-7136-4492-ad94-a60603bc3c38", "f60e8466-7136-4492-ad94-a60603bc3c38"]) // Multi-Download ZIP
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="handleSingle">Einzeldatei</button>
|
||||
<button @click="handleMulti">Mehrere als ZIP</button>
|
||||
</template>
|
||||
@@ -5,9 +5,7 @@ import '@vuepic/vue-datepicker/dist/main.css'
|
||||
import {setPageLayout} from "#app";
|
||||
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
@@ -4,9 +4,7 @@ import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
|
||||
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
|
||||
@@ -67,9 +67,7 @@
|
||||
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
|
||||
@@ -7,9 +7,7 @@ import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
24
plugins/api.ts
Normal file
24
plugins/api.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
const api = $fetch.create({
|
||||
baseURL: "https://backend.fedeo.io",
|
||||
credentials: "include",
|
||||
onRequest({ options }) {
|
||||
// Token aus Cookie holen
|
||||
let token = useCookie("token").value
|
||||
|
||||
// Falls im Request explizit ein anderer JWT übergeben wird → diesen verwenden
|
||||
if (options.context && (options.context as any).jwt) {
|
||||
token = (options.context as any).jwt
|
||||
}
|
||||
|
||||
if (token) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return { provide: { api } }
|
||||
})
|
||||
4
plugins/auth-init.client.ts
Normal file
4
plugins/auth-init.client.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default defineNuxtPlugin(async () => {
|
||||
const auth = useAuthStore()
|
||||
await auth.init()
|
||||
})
|
||||
94
stores/auth.ts
Normal file
94
stores/auth.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { defineStore } from "pinia"
|
||||
import router from "#app/plugins/router";
|
||||
|
||||
export const useAuthStore = defineStore("auth", {
|
||||
state: () => ({
|
||||
user: null as null | { user_id: string; email: string; tenant_id?: string; role?: string },
|
||||
profile: null as null | any,
|
||||
tenants: [] as { tenant_id: string; role: string; tenants: { id: string; name: string } }[],
|
||||
permissions: [] as string[],
|
||||
activeTenant: null as any,
|
||||
activeTenantData: null as any,
|
||||
loading: true as boolean,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async init(token) {
|
||||
await this.fetchMe(token)
|
||||
},
|
||||
|
||||
async login(email: string, password: string) {
|
||||
const { token } = await useNuxtApp().$api("/auth/login", {
|
||||
method: "POST",
|
||||
body: { email, password }
|
||||
})
|
||||
useCookie("token").value = token // persistieren
|
||||
await this.fetchMe(token)
|
||||
},
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
await useNuxtApp().$api("/auth/logout", { method: "POST" })
|
||||
} catch (e) {
|
||||
console.error("Logout fehlgeschlagen:", e)
|
||||
}
|
||||
this.user = null
|
||||
this.permissions = []
|
||||
this.profile = null
|
||||
this.activeTenant = null
|
||||
this.tenants = []
|
||||
useCookie("token").value = null
|
||||
navigateTo("/login")
|
||||
},
|
||||
|
||||
async fetchMe(jwt= null) {
|
||||
try {
|
||||
const me = await useNuxtApp().$api("/api/me", {
|
||||
headers: { Authorization: `Bearer ${jwt}`,
|
||||
context: {
|
||||
jwt
|
||||
}}
|
||||
})
|
||||
console.log(me)
|
||||
this.user = me.user
|
||||
this.permissions = me.permissions
|
||||
this.tenants = me.tenants
|
||||
|
||||
this.tenants.sort(function (a, b) {
|
||||
if (a.id < b.id) return -1
|
||||
if (a.id > b.id) return 1
|
||||
})
|
||||
|
||||
this.profile = me.profile
|
||||
|
||||
if(me.activeTenant) {
|
||||
this.activeTenant = me.activeTenant
|
||||
this.activeTenantData = me.tenants.find(i => i.id === me.activeTenant)
|
||||
this.loading = false
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
|
||||
} catch (err: any) {
|
||||
if (err?.response?.status === 401) this.logout()
|
||||
}
|
||||
},
|
||||
|
||||
async switchTenant(tenant_id: string) {
|
||||
this.loading = true
|
||||
const { token } = await useNuxtApp().$api("/api/tenant/switch", {
|
||||
method: "POST",
|
||||
body: { tenant_id }
|
||||
})
|
||||
useCookie("token").value = token
|
||||
await this.init(token)
|
||||
navigateTo("/")
|
||||
},
|
||||
|
||||
hasPermission(key: string) {
|
||||
return this.permissions.includes(key)
|
||||
}
|
||||
}
|
||||
})
|
||||
1244
stores/data.js
1244
stores/data.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user