Merge branch 'devCorrected' into 'beta'
Dev corrected See merge request fedeo/software!11
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
||||
|
||||
const toast = useToast()
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const modal = useModal()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
@@ -172,13 +172,10 @@ const moveFile = async () => {
|
||||
</template>
|
||||
<div class="flex flex-row">
|
||||
<div :class="useCapacitor().getIsNative() ? ['w-full'] : ['w-1/3']">
|
||||
<object
|
||||
class="bigPreview"
|
||||
:data="`${props.documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
|
||||
type="application/pdf"
|
||||
v-if="props.documentData.path.toLowerCase().includes('pdf')"
|
||||
<PDFViewer
|
||||
v-if="props.documentData.id && props.documentData.path.toLowerCase().includes('pdf')"
|
||||
:file-id="props.documentData.id" />
|
||||
|
||||
/>
|
||||
<img
|
||||
class=" w-full"
|
||||
:src="props.documentData.url"
|
||||
|
||||
@@ -48,7 +48,6 @@ defineShortcuts({
|
||||
},
|
||||
})
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const dataStore = useDataStore()
|
||||
@@ -150,9 +149,9 @@ const loadOptions = async () => {
|
||||
|
||||
for await(const option of optionsToLoad) {
|
||||
if(option.option === "countrys") {
|
||||
loadedOptions.value[option.option] = (await supabase.from("countrys").select()).data
|
||||
loadedOptions.value[option.option] = useEntities("countrys").selectSpecial()
|
||||
} else if(option.option === "units") {
|
||||
loadedOptions.value[option.option] = (await supabase.from("units").select()).data
|
||||
loadedOptions.value[option.option] = useEntities("units").selectSpecial()
|
||||
} else {
|
||||
loadedOptions.value[option.option] = (await useEntities(option.option).select())
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import {useSum} from "~/composables/useSum.js";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
defineShortcuts({
|
||||
/*'/': () => {
|
||||
//console.log(searchinput)
|
||||
|
||||
@@ -18,11 +18,11 @@ const links = [{
|
||||
onClick: () => {
|
||||
shortcuts.value = true
|
||||
}
|
||||
}, {
|
||||
},/* {
|
||||
label: 'Tickets',
|
||||
icon: 'i-heroicons-clipboard-document',
|
||||
to: '/support',
|
||||
}, {
|
||||
},*/ {
|
||||
label: 'Webseite',
|
||||
icon: 'i-heroicons-globe-europe-africa',
|
||||
to: 'https://fedeo.de',
|
||||
|
||||
@@ -29,10 +29,8 @@ 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 || []
|
||||
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 || []
|
||||
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -152,11 +152,11 @@ const links = computed(() => {
|
||||
to: "/standardEntity/absencerequests",
|
||||
icon: "i-heroicons-document-text"
|
||||
}] : [],
|
||||
{
|
||||
/*{
|
||||
label: "Fahrten",
|
||||
to: "/trackingTrips",
|
||||
icon: "i-heroicons-map"
|
||||
},
|
||||
},*/
|
||||
]
|
||||
},
|
||||
... [{
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from "vue"
|
||||
import { VPdfViewer } from "@vue-pdf-viewer/viewer"
|
||||
import { VPdfViewer, useLicense } from "@vue-pdf-viewer/viewer"
|
||||
|
||||
const props = defineProps({
|
||||
// Beispiel: "FEDEO/26/filesbyid/11990345-8711-4e23-8851-c50f028fc915/RE25-1081.pdf"
|
||||
fileId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
uri: {
|
||||
type: String,
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
@@ -14,6 +16,9 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
useLicense(config.public.pdfViewerLicense || "eyJkYXRhIjoiZXlKMElqb2laR1YyWld4dmNHVnlJaXdpWVhaMUlqb3hOemt3TmpNNU9UazVMQ0prYlNJNklteHZZMkZzYUc5emREb3pNREF3SWl3aWJpSTZJbVkxT1RRM1ptWTBOVFJsTkdKaU16a2lMQ0psZUhBaU9qRTNPVEEyTXprNU9Ua3NJbVJ0ZENJNkluTndaV05wWm1saklpd2ljQ0k2SW5acFpYZGxjaUo5Iiwic2lnbmF0dXJlIjoibGVpVmNkbENzQStiNCt5ODBNVnZzY2lHcEpjZ3F4T2M1R2V3VFJJVzY5SkQwSGxteERRcmtmSzQwOWhzYXdJN2pSVXM3QmdvNzJIU3ZYRmZtS1Rhejl1N0o0eEt0Nk1VL2E5cjFMblA1aUZzZFBtemRMbFJFSDJoSG00Zmk2Y25UbEhnQTdHekkrL2JVVlJhUEJXYWFDdlFSK3ByRHg2NHl4aFhjOElPT2swb1pldWtKc2tCTjNCS0NiZ3VkSGhmbXB5TE9WUDc4SUR4U1FNR3BOU2I4eWRqakdQSHBuNjZLQWVOWGpRc05EbHNIdmJkaFFyNk1ZaldzV3JkRm50ditPR2FyT2MwQzJLNVJmZkRKWTBldVpXOFNiRTRLNlJkWTVSY084eVpBNzcrQWVlV0hOdHg0SllhV1hFMW5sYmZLZDVvM3V6b0dLTFYzODBUMmlvSERnPT0ifQ==")
|
||||
|
||||
const pdfSrc = ref(null) // ObjectURL fürs Viewer
|
||||
const { $api } = useNuxtApp()
|
||||
|
||||
@@ -30,7 +35,16 @@ async function loadPdf(id) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadPdf(props.fileId))
|
||||
onMounted(() => {
|
||||
if(props.fileId) {
|
||||
loadPdf(props.fileId)
|
||||
} else if(props.uri) {
|
||||
pdfSrc.value = props.uri
|
||||
}
|
||||
|
||||
//window.addEventListener("resize", handleZoomTool("pageWidth"), true);
|
||||
|
||||
})
|
||||
watch(() => props.fileId, (newPath) => {
|
||||
if (newPath) loadPdf(newPath)
|
||||
})
|
||||
@@ -38,6 +52,7 @@ watch(() => props.fileId, (newPath) => {
|
||||
const vpvRef = ref(null);
|
||||
|
||||
//Zoom Control
|
||||
|
||||
const zoomControl = computed(() => vpvRef.value?.zoomControl)
|
||||
|
||||
const currentScale = computed(() => {
|
||||
@@ -45,6 +60,7 @@ const currentScale = computed(() => {
|
||||
})
|
||||
|
||||
const handleZoomTool = (type) => {
|
||||
console.log(type)
|
||||
const zoomCtrl = unref(zoomControl)
|
||||
if (!zoomCtrl) return
|
||||
|
||||
@@ -57,7 +73,6 @@ const handleZoomTool = (type) => {
|
||||
zoomCtrl.zoom(type)
|
||||
}
|
||||
}
|
||||
|
||||
//Page Control
|
||||
const pageControl = computed(() => vpvRef.value?.pageControl)
|
||||
const currentPageInput = computed(() => pageControl.value?.currentPage)
|
||||
@@ -93,10 +108,12 @@ const handleKeyPress = (event) => {
|
||||
//Handle Download
|
||||
const downloadControl = computed(() => vpvRef.value?.downloadControl)
|
||||
|
||||
const handleDownloadFile = () => {
|
||||
const downloadCtrl = unref(downloadControl)
|
||||
const handleDownloadFile = async () => {
|
||||
await useFiles().downloadFile(props.fileId)
|
||||
|
||||
/*const downloadCtrl = unref(downloadControl)
|
||||
if (!downloadCtrl) return
|
||||
downloadCtrl.download()
|
||||
downloadCtrl.download()*/
|
||||
}
|
||||
|
||||
watch(downloadControl, (downloadCtrl) => {
|
||||
@@ -136,6 +153,11 @@ watch(downloadControl, (downloadCtrl) => {
|
||||
icon="i-heroicons-magnifying-glass-plus"
|
||||
variant="outline"
|
||||
></UButton>
|
||||
<UButton
|
||||
@click="handleDownloadFile"
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-down-on-square"
|
||||
/>
|
||||
<UButton
|
||||
@click="prevPage"
|
||||
:disabled="isPreviousPageButtonDisable"
|
||||
@@ -143,11 +165,7 @@ watch(downloadControl, (downloadCtrl) => {
|
||||
variant="outline"
|
||||
></UButton>
|
||||
|
||||
<UButton
|
||||
@click="handleDownloadFile"
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-down-on-square"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Page number input and total pages display -->
|
||||
<div class="flex items-center text-sm font-normal">
|
||||
@@ -179,7 +197,11 @@ watch(downloadControl, (downloadCtrl) => {
|
||||
:toolbar-options="false"
|
||||
ref="vpvRef"
|
||||
/>
|
||||
<div v-else>Lade PDF…</div>
|
||||
<div v-else>
|
||||
<UProgress
|
||||
class="mt-5 w-2/3 mx-auto"
|
||||
animation="carousel"></UProgress>
|
||||
</div>
|
||||
</div></template>
|
||||
|
||||
<style scoped>
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
let unallocatedStatements = ref(0)
|
||||
let bankaccounts = ref([])
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import {useDataStore} from "~/stores/data.js";
|
||||
|
||||
|
||||
export const useSupabaseSelect = async (relation,select = '*', sortColumn = null, ascending = true,noArchivedFiltering = false) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const profileStore = useProfileStore()
|
||||
let data = null
|
||||
const dataStore = useDataStore()
|
||||
|
||||
const dataType = dataStore.dataTypes[relation]
|
||||
|
||||
if(sortColumn !== null ) {
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.order(sortColumn, {ascending: ascending})).data
|
||||
} else {
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", profileStore.currentTenant)).data
|
||||
}
|
||||
|
||||
if(dataType && dataType.isArchivable && !noArchivedFiltering) {
|
||||
data = data.filter(i => !i.archived)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export const useSupabaseSelectSingle = async (relation,idToEq,select = '*' ) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const profileStore = useProfileStore()
|
||||
let data = null
|
||||
|
||||
|
||||
if(idToEq !== null) {
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.eq("id",idToEq)
|
||||
.single()).data
|
||||
} else {
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.single()).data
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -7,9 +7,8 @@ services:
|
||||
# networks:
|
||||
# - traefik
|
||||
environment:
|
||||
SUPABASE_URL: "https://uwppvcxflrcsibuzsbil.supabase.co"
|
||||
SUPABASE_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo"
|
||||
# labels:
|
||||
NUXT_PUBLIC_API_BASE: "https://backend.fedeo.io"
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.docker.network=traefik"
|
||||
# - "traefik.port=3000"
|
||||
|
||||
@@ -57,5 +57,13 @@ export default defineNuxtConfig({
|
||||
prefix: "Tiptap"
|
||||
},
|
||||
|
||||
runtimeConfig: {
|
||||
|
||||
public: {
|
||||
apiBase: '',
|
||||
pdfviewerLicense: ''
|
||||
}
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-12-18'
|
||||
})
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
|
||||
const searchString = ref("")
|
||||
const showAssigned = ref(false)
|
||||
const selectedAccount = ref(0)
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
let statements = dataStore.bankStatements
|
||||
|
||||
if(!showAssigned.value) {
|
||||
statements = statements.filter(statement => !statement.customerInvoice || !statement.vendorInvoice)
|
||||
}
|
||||
|
||||
if(selectedAccount.value !== 0) {
|
||||
statements = statements.filter(statement => statement.account === selectedAccount.value)
|
||||
}
|
||||
|
||||
if(searchString.value.length > 0) {
|
||||
statements = statements.filter(item => {
|
||||
return Object.values(item).some((value) => {
|
||||
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return statements
|
||||
})
|
||||
|
||||
|
||||
|
||||
const showStatementSlideover = ref(false)
|
||||
const selectedStatement = ref({})
|
||||
|
||||
const selectStatement = (statement) => {
|
||||
selectedStatement.value = statement
|
||||
showStatementSlideover.value = true
|
||||
}
|
||||
|
||||
const statementColumns = [
|
||||
{
|
||||
key:"amount",
|
||||
label: "Betrag"
|
||||
},
|
||||
{
|
||||
key:"date",
|
||||
label: "Datum",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "credName",
|
||||
label: "Empfänger"
|
||||
},
|
||||
{
|
||||
key: "debName",
|
||||
label: "Sender"
|
||||
},
|
||||
{
|
||||
key: "text",
|
||||
label: "Verwendungszweck"
|
||||
},
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USlideover
|
||||
v-model="showStatementSlideover"
|
||||
>
|
||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
|
||||
|
||||
|
||||
|
||||
<DevOnly>
|
||||
{{selectedStatement}}
|
||||
</DevOnly>
|
||||
</UCard>
|
||||
|
||||
</USlideover>
|
||||
|
||||
<InputGroup :gap="2">
|
||||
<UInput
|
||||
v-model="searchString"
|
||||
placeholder="Suche..."
|
||||
/>
|
||||
<USelectMenu
|
||||
:options="dataStore.bankAccounts.filter(account => account.used)"
|
||||
v-model="selectedAccount"
|
||||
option-attribute="iban"
|
||||
value-attribute="id"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.bankAccounts.find(account => account.id === selectedAccount) ? dataStore.bankAccounts.find(account => account.id === selectedAccount).iban : "Kontoauswählen"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<UCheckbox
|
||||
v-model="showAssigned"
|
||||
label="Zugeordnete Anzeigen"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="statementColumns"
|
||||
@select="selectStatement"
|
||||
>
|
||||
<template #amount-data="{row}">
|
||||
<span
|
||||
v-if="row.amount >= 0"
|
||||
class="text-primary-500"
|
||||
>
|
||||
{{row.amount.toFixed(2) + " €"}}
|
||||
</span>
|
||||
<span
|
||||
v-if="row.amount < 0"
|
||||
class="text-rose-600"
|
||||
>
|
||||
{{row.amount.toFixed(2) + " €"}}
|
||||
</span>
|
||||
</template>
|
||||
<template #date-data="{row}">
|
||||
{{dayjs(row.date).format("DD.MM.YY")}}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,119 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const profiles = ref([])
|
||||
|
||||
const itemInfo = ref({})
|
||||
const selectedProfiles = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
profiles.value = await useSupabaseSelect("profiles")
|
||||
selectedProfiles.value = [profileStore.activeProfile.id]
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const createChat = async () => {
|
||||
const {data,error} = await supabase.from("chats").insert({
|
||||
tenant: profileStore.currentTenant,
|
||||
name: itemInfo.value.name
|
||||
}).select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else if(data){
|
||||
console.log(data)
|
||||
|
||||
let memberships = selectedProfiles.value.map(i => {
|
||||
return {
|
||||
profile_id: i,
|
||||
chat_id: data[0].id
|
||||
}
|
||||
})
|
||||
|
||||
const {error} = await supabase.from("chatmembers").insert(memberships)
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
navigateTo("/chats")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar>
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="navigateTo(`/chats`)"
|
||||
>
|
||||
Chats
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
class="text-xl font-medium"
|
||||
>Chat Erstellen</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="createChat"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="navigateTo(`/chats`)"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<UForm>
|
||||
<UFormGroup
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
<UFormGroup
|
||||
label="Beteiligte Benutzer:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="selectedProfiles"
|
||||
:options="profiles"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
multiple
|
||||
:search-attributes="['fullName']"
|
||||
>
|
||||
<template #label>
|
||||
{{selectedProfiles.length > 0 ? selectedProfiles.map(i => profileStore.getProfileById(i).fullName).join(", ") : "Keine Benutzer ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,123 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/chats/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/chats/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setup = async () => {
|
||||
items.value = await useSupabaseSelect("chats","*, profiles(*)")
|
||||
//items.value = await useSupabaseSelect("chats","*, profiles(*), chatmessages(*)")
|
||||
}
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Name"
|
||||
},{
|
||||
key: "profiles",
|
||||
label: "Mitglieder"
|
||||
},
|
||||
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
|
||||
return useListFilter(searchString.value, items.value)
|
||||
})
|
||||
|
||||
setup()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar title="Chats" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/chats/create`)">+ Chat</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/chats/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Chats anzuzeigen' }"
|
||||
>
|
||||
<template #name-data="{row}">
|
||||
<span class="text-primary-500 font-bold" v-if="row === filteredRows[selectedItem]">{{row.name}}</span>
|
||||
<span v-else>{{row.name}}</span>
|
||||
</template>
|
||||
<template #profiles-data="{row}">
|
||||
{{row.profiles.map(i => i.fullName).join(", ")}}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,120 +0,0 @@
|
||||
<script setup>
|
||||
import { format, isToday } from 'date-fns'
|
||||
import dayjs from "dayjs"
|
||||
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
' ': () => {
|
||||
document.getElementById("textinput").focus()
|
||||
},
|
||||
})
|
||||
|
||||
const itemInfo = ref({})
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const setup = async () => {
|
||||
itemInfo.value = await useSupabaseSelectSingle("chats",useRoute().params.id,"*, profiles(*), chatmessages(*)")
|
||||
|
||||
let unseenMessages = itemInfo.value.chatmessages.filter(i => !i.seenBy.includes(profileStore.activeProfile.id))
|
||||
|
||||
|
||||
for await (const message of unseenMessages){
|
||||
await supabase.from("chatmessages").update({seenBy: [...message.seenBy, profileStore.activeProfile.id]}).eq("id",message.id)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
setup()
|
||||
|
||||
const messageText = ref("")
|
||||
const sendMessage = async () => {
|
||||
if(messageText.value.length > 0) {
|
||||
const message = {
|
||||
origin: profileStore.activeProfile.id,
|
||||
destinationchat: itemInfo.value.id,
|
||||
text: messageText.value,
|
||||
tenant: profileStore.currentTenant,
|
||||
seenBy: [profileStore.activeProfile.id]
|
||||
}
|
||||
|
||||
const {data,error} = await supabase.from("chatmessages").insert(message)
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
//Reset
|
||||
messageText.value = ""
|
||||
//Create Notifications
|
||||
let notifications = itemInfo.value.profiles.filter(i => i.id !== profileStore.activeProfile.id).map(i => {
|
||||
return {
|
||||
tenant: profileStore.currentTenant,
|
||||
profile: i.id,
|
||||
initiatingProfile: profileStore.activeProfile.id,
|
||||
title: `Sie haben eine neue Nachricht im Chat ${itemInfo.value.name}`,
|
||||
link: `/chats/show/${itemInfo.value.id}`,
|
||||
message: message.text
|
||||
}
|
||||
})
|
||||
|
||||
console.log(notifications)
|
||||
|
||||
const {error} = await supabase.from("notifications").insert(notifications)
|
||||
|
||||
setup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="itemInfo.name">
|
||||
<template #center>
|
||||
|
||||
</template>
|
||||
<template #right>
|
||||
<UAvatarGroup size="sm" :max="2">
|
||||
<UAvatar
|
||||
v-if="itemInfo.profiles"
|
||||
v-for="avatar in itemInfo.profiles.map(i => i.fullName)"
|
||||
:alt="avatar" size="sm" />
|
||||
</UAvatarGroup>
|
||||
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<div class="scrollList p-5">
|
||||
<UAlert
|
||||
:color="message.seenBy.includes(profileStore.activeProfile.id) ? 'white' : 'primary'"
|
||||
:variant="message.seenBy.includes(profileStore.activeProfile.id) ? 'solid' : 'outline'"
|
||||
class="my-2"
|
||||
v-for="message in itemInfo.chatmessages"
|
||||
:description="message.text"
|
||||
:avatar="{ alt: profileStore.getProfileById(message.origin).fullName }"
|
||||
:title="`${profileStore.getProfileById(message.origin).fullName} - ${isToday(new Date(message.created_at)) ? dayjs(message.created_at).format('HH:mm') : dayjs(message.created_at).format('DD.MM.YYYY HH:mm')}`"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div class="flex flex-row justify-between p-5">
|
||||
<UInput
|
||||
class="flex-auto mr-2"
|
||||
v-model="messageText"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="Deine Nachricht"
|
||||
id="textinput"
|
||||
/>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-double-right-solid"
|
||||
@click="sendMessage"
|
||||
:disabled="messageText.length === 0"
|
||||
>
|
||||
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -134,7 +134,6 @@ const setupPage = async () => {
|
||||
|
||||
if (route.query.loadMode === "deliveryNotes") {
|
||||
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||
//let linkedDocuments = (await supabase.from("createddocuments").select().in("id", JSON.parse(route.query.linkedDocuments))).data
|
||||
|
||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||
|
||||
@@ -203,7 +202,6 @@ const setupPage = async () => {
|
||||
|
||||
} else if (route.query.loadMode === "finalInvoice") {
|
||||
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||
//let linkedDocuments = (await supabase.from("createddocuments").select().in("id", JSON.parse(route.query.linkedDocuments))).data
|
||||
|
||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||
|
||||
@@ -3030,12 +3028,17 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
||||
>
|
||||
Show
|
||||
</UButton>-->
|
||||
<object
|
||||
<PDFViewer
|
||||
v-if="showDocument"
|
||||
:uri="uri"
|
||||
/>
|
||||
|
||||
<!-- <object
|
||||
:data="uri"
|
||||
v-if="showDocument"
|
||||
type="application/pdf"
|
||||
class="w-full previewDocumentMobile"
|
||||
/>
|
||||
/>-->
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -19,7 +19,7 @@ const itemInfo = ref({})
|
||||
const linkedDocument =ref({})
|
||||
const setupPage = async () => {
|
||||
if(route.params) {
|
||||
if(route.params.id) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*, files(*), linkedDocument(*)")
|
||||
if(route.params.id) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*,files(*),linkedDocument(*)")
|
||||
|
||||
console.log(itemInfo.value)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
//TODO: BACKENDCHANGE EMAIL SENDING
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
@@ -24,7 +23,9 @@ const loadedDocuments = ref([])
|
||||
const loaded = ref(false)
|
||||
const noAccountsPresent = ref(false)
|
||||
const setupPage = async () => {
|
||||
emailAccounts.value = await useEntities("emailAccounts").select()
|
||||
//emailAccounts.value = await useEntities("emailAccounts").select()
|
||||
|
||||
emailAccounts.value = await useNuxtApp().$api("/api/email/accounts")
|
||||
|
||||
if(emailAccounts.value.length === 0) {
|
||||
noAccountsPresent.value = true
|
||||
@@ -130,7 +131,6 @@ const sendEmail = async () => {
|
||||
|
||||
for await (const doc of loadedDocuments.value) {
|
||||
|
||||
//const {data,error} = await supabase.storage.from("filesdev").download(doc.path)
|
||||
|
||||
const res = await useFiles().downloadFile(doc.id, null, true)
|
||||
|
||||
@@ -143,7 +143,9 @@ const sendEmail = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
const res = await useNuxtApp().$api("/api/emailasuser/send",{
|
||||
console.log(body)
|
||||
|
||||
const res = await useNuxtApp().$api("/api/email/send",{
|
||||
method: "POST",
|
||||
body: body,
|
||||
})
|
||||
@@ -151,9 +153,6 @@ const sendEmail = async () => {
|
||||
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"})
|
||||
|
||||
@@ -208,7 +207,7 @@ const sendEmail = async () => {
|
||||
>
|
||||
<USelectMenu
|
||||
:options="emailAccounts"
|
||||
option-attribute="emailAddress"
|
||||
option-attribute="email"
|
||||
value-attribute="id"
|
||||
v-model="emailData.account"
|
||||
/>
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<script setup>
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
|
||||
const currentSubmission = (await supabase.from("formSubmits").select().eq('id',route.params.id)).data[0]
|
||||
const form = (await supabase.from("forms").select().eq('id',currentSubmission.formType)).data[0]
|
||||
|
||||
const formData = ref({})
|
||||
const submitted = ref(currentSubmission.submitted)
|
||||
|
||||
const submitForm = async () => {
|
||||
|
||||
submitted.value = true
|
||||
|
||||
console.log(formData.value)
|
||||
const {data,error} = await supabase
|
||||
.from("formSubmits")
|
||||
.update({values: formData.value, submitted: true})
|
||||
.eq('id',currentSubmission.id)
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else if( data) {
|
||||
formData.value = {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UForm
|
||||
v-if="!submitted"
|
||||
@submit="submitForm"
|
||||
@reset="formData = {}"
|
||||
>
|
||||
<div
|
||||
v-for="item in form.fields"
|
||||
>
|
||||
<p v-if="item.type === 'header'">{{item.label}}</p>
|
||||
<UFormGroup
|
||||
v-else-if="item.type.includes('Input')"
|
||||
:label="item.required ? item.label + '*' : item.label"
|
||||
>
|
||||
<UInput
|
||||
v-if="item.type === 'textInput'"
|
||||
v-model="formData[item.key]"
|
||||
:required="item.required"
|
||||
/>
|
||||
<UInput
|
||||
v-else-if="item.type === 'numberInput'"
|
||||
v-model="formData[item.key]"
|
||||
:required="item.required"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
<UButton type="submit">
|
||||
Abschicken
|
||||
</UButton>
|
||||
<UButton
|
||||
type="reset"
|
||||
color="rose"
|
||||
class="m-2"
|
||||
>
|
||||
Zurücksetzen
|
||||
</UButton>
|
||||
|
||||
|
||||
</UForm>
|
||||
<div v-else>
|
||||
Dieses Formular wurde bereits abgeschickt. Möchten Sie erneut Daten abschicken, sprechen Sie bitte Ihren Ansprechpartner an, um das Formular freizuschalten.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,674 +0,0 @@
|
||||
<script setup>
|
||||
import InputGroup from "~/components/InputGroup.vue";
|
||||
import dayjs from "dayjs";
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
|
||||
const itemInfo = ref({
|
||||
vendor: 0,
|
||||
expense: true,
|
||||
reference: "",
|
||||
date: null,
|
||||
dueDate: null,
|
||||
paymentType: "Überweisung",
|
||||
description: "",
|
||||
state: "Entwurf",
|
||||
accounts: [
|
||||
{
|
||||
account: null,
|
||||
amountNet: null,
|
||||
amountTax: null,
|
||||
taxType: "19",
|
||||
costCentre: null
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const availableDocuments = ref([])
|
||||
const costcentres = ref([])
|
||||
const accounts = ref([])
|
||||
const vendors = ref([])
|
||||
const setup = async () => {
|
||||
let filetype = (await supabase.from("filetags").select().eq("tenant",profileStore.currentTenant).eq("incomingDocumentType","invoices").single()).data.id
|
||||
console.log(filetype)
|
||||
let ids = (await supabase.from("files").select("id").eq("tenant",profileStore.currentTenant).eq("type", filetype).is("incominginvoice",null)).data.map(i => i.id)
|
||||
availableDocuments.value = await useFiles().selectSomeDocuments(ids)
|
||||
accounts.value = (await supabase.from("accounts").select().order("number",{ascending:true})).data
|
||||
vendors.value = await useSupabaseSelect("vendors")
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const loadCostCentres = async () => {
|
||||
costcentres.value = await useSupabaseSelect("costcentres")
|
||||
|
||||
}
|
||||
loadCostCentres()
|
||||
|
||||
const useNetMode = ref(false)
|
||||
|
||||
const loadedFile = ref(null)
|
||||
const loadFile = async (id) => {
|
||||
console.log(id)
|
||||
loadedFile.value = await useFiles().selectDocument(id)
|
||||
console.log(loadedFile.value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const changeNetMode = (mode) => {
|
||||
useNetMode.value = mode
|
||||
|
||||
itemInfo.value.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const taxOptions = ref([
|
||||
{
|
||||
label: "19% USt",
|
||||
percentage: 19,
|
||||
key: "19"
|
||||
},{
|
||||
label: "7% USt",
|
||||
percentage: 7,
|
||||
key: "7"
|
||||
},{
|
||||
label: "Innergemeintschaftlicher Erwerb 19%",
|
||||
percentage: 0,
|
||||
key: "19I"
|
||||
},{
|
||||
label: "Innergemeintschaftlicher Erwerb 7%",
|
||||
percentage: 0,
|
||||
key: "7I"
|
||||
},{
|
||||
label: "§13b UStG",
|
||||
percentage: 0,
|
||||
key: "13B"
|
||||
},{
|
||||
label: "Keine USt",
|
||||
percentage: 0,
|
||||
key: "null"
|
||||
},
|
||||
])
|
||||
|
||||
|
||||
const totalCalculated = computed(() => {
|
||||
let totalNet = 0
|
||||
let totalAmount19Tax = 0
|
||||
let totalAmount7Tax = 0
|
||||
let totalAmount0Tax = 0
|
||||
let totalGross = 0
|
||||
|
||||
itemInfo.value.accounts.forEach(account => {
|
||||
if(account.amountNet) totalNet += account.amountNet
|
||||
|
||||
if(account.taxType === "19" && account.amountTax) {
|
||||
totalAmount19Tax += account.amountTax
|
||||
}
|
||||
})
|
||||
|
||||
totalGross = Number(totalNet + totalAmount19Tax)
|
||||
|
||||
return {
|
||||
totalNet,
|
||||
totalAmount19Tax,
|
||||
totalGross
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const createIncomingInvoice = async () => {
|
||||
const data = await dataStore.createNewItem('incominginvoices',itemInfo.value,true)
|
||||
|
||||
const {error} = await supabase.from("files").update({incominginvoice: data.id}).eq("id",loadedFile.value.id)
|
||||
|
||||
router.push(`/incominginvoices/show/${data.id}`)
|
||||
|
||||
}
|
||||
|
||||
const setCostCentre = async (item,data) => {
|
||||
await loadCostCentres()
|
||||
item.costCentre = data.id
|
||||
}
|
||||
|
||||
const gptLoading = ref(false)
|
||||
|
||||
const getInvoiceData = async () => {
|
||||
gptLoading.value = true
|
||||
console.log(loadedFile.value)
|
||||
|
||||
|
||||
//loadedFile.value.url
|
||||
|
||||
|
||||
/*let data = {
|
||||
"invoice_number": "3423478673",
|
||||
"invoice_date": "2025-05-30",
|
||||
"invoice_type": "incoming",
|
||||
"delivery_type": "null",
|
||||
"delivery_note_number": "null",
|
||||
"reference": "null",
|
||||
"issuer": {
|
||||
"name": "Boels Rental Germany GmbH",
|
||||
"address": "Emeranstraße 49-51, 85622 Feldkirchen, Deutschland",
|
||||
"phone": "+49-(0)1801663225",
|
||||
"email": "fakturierung@boels.de",
|
||||
"bank": "ABN AMRO Bank N.V.",
|
||||
"bic": "ABNANL2A",
|
||||
"iban": "NL09 ABNA 0520 5585 61"
|
||||
},
|
||||
"recipient": {
|
||||
"name": "Federspiel Technology UG",
|
||||
"address": "Am Schwarzen Brack 14, 26452 Sande, Deutschland",
|
||||
"phone": "null",
|
||||
"email": "null"
|
||||
},
|
||||
"invoice_items": [
|
||||
{
|
||||
"description": "Bautrockner 50 ltr.",
|
||||
"unit": "piece",
|
||||
"quantity": 1,
|
||||
"total": 395.22
|
||||
},
|
||||
{
|
||||
"description": "Servicepauschale Kat. A",
|
||||
"unit": "piece",
|
||||
"quantity": 1,
|
||||
"total": 32.1
|
||||
},
|
||||
{
|
||||
"description": "Haftungsbegrenzung A: (Schäden, exkl. Feuer/Diebstahl/Einbruch)",
|
||||
"unit": "piece",
|
||||
"quantity": 1,
|
||||
"total": 3.2
|
||||
},
|
||||
{
|
||||
"description": "Haftungsbegrenzung B: (Feuer/Diebstahl/Einbruch)",
|
||||
"unit": "piece",
|
||||
"quantity": 1,
|
||||
"total": 16.93
|
||||
}
|
||||
],
|
||||
"subtotal": 89.1,
|
||||
"tax_rate": 19,
|
||||
"tax": 16.93,
|
||||
"total": 106.03,
|
||||
"terms": "Dieser Betrag wird automatisch mittels Lastschrift von ihrem Konto eingezogen"
|
||||
}
|
||||
|
||||
console.log(data)
|
||||
console.log(data.subtotal)*/
|
||||
|
||||
|
||||
|
||||
let data = await useFunctions().useGetInvoiceData(loadedFile.value)
|
||||
|
||||
|
||||
|
||||
|
||||
if(data.invoice_number) itemInfo.value.reference = data.invoice_number
|
||||
if(data.invoice_date) itemInfo.value.date = dayjs(data.invoice_date)
|
||||
if(data.issuer.id) itemInfo.value.vendor = data.issuer.id
|
||||
if(data.invoice_duedate) itemInfo.value.dueDate = dayjs(data.invoice_duedate)
|
||||
if(data.terms) itemInfo.value.paymentType = data.terms
|
||||
if(data.subtotal) {
|
||||
itemInfo.value.accounts = [
|
||||
{
|
||||
account: null,
|
||||
amountNet: data.subtotal,
|
||||
amountTax: data.tax,
|
||||
taxType: String(data.tax_rate),
|
||||
costCentre: null,
|
||||
amountGross: Number(data.subtotal) + Number(data.tax)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if(data.terms === "Direct Debit") {
|
||||
itemInfo.value.paymentType = "Einzug"
|
||||
} else if(data.terms === "Transfer") {
|
||||
itemInfo.value.paymentType = "Überweisung"
|
||||
} else if(data.terms === "Credit Card") {
|
||||
itemInfo.value.paymentType = "Kreditkarte"
|
||||
} else if(data.terms === "Other") {
|
||||
itemInfo.value.paymentType = "Sonstiges"
|
||||
}
|
||||
|
||||
let description = ""
|
||||
|
||||
if(data.delivery_note_number) description += `Lieferschein: ${data.delivery_note_number} \n`
|
||||
if(data.reference) description += `Referenz: ${data.reference} \n`
|
||||
if(data.invoice_items) {
|
||||
data.invoice_items.forEach(item => {
|
||||
description += `${item.description} - ${item.quantity} ${item.unit} - ${item.total}\n`
|
||||
})
|
||||
}
|
||||
itemInfo.value.description = description
|
||||
|
||||
|
||||
|
||||
|
||||
gptLoading.value = false
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="createIncomingInvoice"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<div v-if="!loadedFile">
|
||||
<DocumentList
|
||||
v-if="availableDocuments.length > 0"
|
||||
:documents="availableDocuments"
|
||||
:return-document-id="true"
|
||||
@selectDocument="(documentId) => loadFile(documentId)"
|
||||
/>
|
||||
<div v-else class="w-full text-center">
|
||||
<span class="text-xl font-medium mt-10">Keine Dateien zum zuweisen verfügbar</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex justify-between mt-5 workingContainer"
|
||||
>
|
||||
<object
|
||||
v-if="loadedFile"
|
||||
:data="loadedFile.url + '#toolbar=0&navpanes=0&scrollbar=0&statusbar=0&messages=0&scrollbar=0'"
|
||||
type="application/pdf"
|
||||
class="mx-5 documentPreview"
|
||||
/>
|
||||
<div class="w-3/5 mx-5">
|
||||
|
||||
<div v-if="mode === 'show'">
|
||||
|
||||
<div class="truncate mb-5">
|
||||
<p>Status: {{itemInfo.state}}</p>
|
||||
<p>Datum: {{dayjs(itemInfo.date).format('DD.MM.YYYY')}}</p>
|
||||
<p>Fälligkeitsdatum: {{dayjs(itemInfo.dueDate).format('DD.MM.YYYY')}}</p>
|
||||
<p>Lieferant: <nuxt-link :to="`/vendors/show/${itemInfo.vendor}`">{{dataStore.getVendorById(itemInfo.vendor).name}}</nuxt-link></p>
|
||||
<p>Bezahlt: {{itemInfo.paid}}</p>
|
||||
<p>Beschreibung: {{itemInfo.description}}</p>
|
||||
|
||||
<!-- TODO: Buchungszeilen darstellen -->
|
||||
</div>
|
||||
|
||||
<HistoryDisplay
|
||||
type="incomingInvoice"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
:render-headline="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else class=" scrollContainer">
|
||||
<UButton
|
||||
icon="i-heroicons-sparkles"
|
||||
class="my-3"
|
||||
variant="outline"
|
||||
@click="getInvoiceData"
|
||||
:disabled="gptLoading"
|
||||
>
|
||||
KI - Vorschlag
|
||||
<UProgress v-if="gptLoading" animation="carousel"/>
|
||||
</UButton>
|
||||
|
||||
<InputGroup class="mb-3">
|
||||
<UButton
|
||||
:variant="itemInfo.expense ? 'solid' : 'outline'"
|
||||
@click="itemInfo.expense = true"
|
||||
>
|
||||
Ausgabe
|
||||
</UButton>
|
||||
<UButton
|
||||
:variant="!itemInfo.expense ? 'solid' : 'outline'"
|
||||
@click="itemInfo.expense = false"
|
||||
>
|
||||
Einnahme
|
||||
</UButton>
|
||||
</InputGroup>
|
||||
|
||||
<UFormGroup label="Lieferant:" >
|
||||
<InputGroup>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.vendor"
|
||||
:options="dataStore.vendors"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['name','vendorNumber']"
|
||||
class="flex-auto"
|
||||
searchable-placeholder="Suche..."
|
||||
:color="!itemInfo.vendor ? 'rose' : 'primary'"
|
||||
@change="vendors.find(i => i.id === itemInfo.vendor).defaultPaymentMethod ? itemInfo.paymentType = vendors.find(i => i.id === itemInfo.vendor).defaultPaymentMethod : null"
|
||||
>
|
||||
<template #option="{option}">
|
||||
{{option.vendorNumber}} - {{option.name}}
|
||||
</template>
|
||||
<template #label>
|
||||
{{dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor) ? dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor).name : 'Lieferant auswählen'}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<EntityModalButtons
|
||||
type="vendors"
|
||||
:id="itemInfo.vendor"
|
||||
@return-data="(data) => itemInfo.vendor = data.id"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
class="mt-3"
|
||||
label="Rechnungsreferenz:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.reference"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<InputGroup class="mt-3" gap="2">
|
||||
<UFormGroup label="Rechnungsdatum:">
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:color="!itemInfo.date ? 'rose' : 'primary'"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.date" @close="itemInfo.dueDate = itemInfo.date" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Fälligkeitsdatum:">
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
|
||||
<UFormGroup label="Zahlart:" >
|
||||
<USelectMenu
|
||||
:options="['Einzug','Kreditkarte','Überweisung','Sonstiges']"
|
||||
v-model="itemInfo.paymentType"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Beschreibung:" >
|
||||
<UTextarea
|
||||
v-model="itemInfo.description"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<InputGroup class="my-3">
|
||||
<UButton
|
||||
:variant="!useNetMode ? 'solid' : 'outline'"
|
||||
@click="changeNetMode(false)"
|
||||
>
|
||||
Brutto
|
||||
</UButton>
|
||||
<UButton
|
||||
:variant="useNetMode ? 'solid' : 'outline'"
|
||||
@click="changeNetMode(true)"
|
||||
>
|
||||
Netto
|
||||
</UButton>
|
||||
|
||||
|
||||
|
||||
<!-- Brutto
|
||||
<UToggle
|
||||
v-model="useNetMode"
|
||||
@update:model-value="itemInfo.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]"
|
||||
/>
|
||||
Netto-->
|
||||
</InputGroup>
|
||||
|
||||
<table v-if="itemInfo.accounts.length > 1" class="w-full">
|
||||
<tr>
|
||||
<td>Gesamt exkl. Steuer: </td>
|
||||
<td class="text-right">{{totalCalculated.totalNet.toFixed(2).replace(".",",")}} €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19% Steuer: </td>
|
||||
<td class="text-right">{{totalCalculated.totalAmount19Tax.toFixed(2).replace(".",",")}} €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gesamt inkl. Steuer: </td>
|
||||
<td class="text-right">{{totalCalculated.totalGross.toFixed(2).replace(".",",")}} €</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div
|
||||
class="my-3"
|
||||
v-for="(item,index) in itemInfo.accounts"
|
||||
>
|
||||
|
||||
<UFormGroup
|
||||
label="Kategorie"
|
||||
class="mb-3"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="accounts"
|
||||
option-attribute="label"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['label']"
|
||||
searchable-placeholder="Suche..."
|
||||
v-model="item.account"
|
||||
:color="!item.account ? 'rose' : 'primary'"
|
||||
>
|
||||
<template #label>
|
||||
{{accounts.find(account => account.id === item.account) ? accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}
|
||||
</template>
|
||||
<template #option="{option}">
|
||||
{{option.number}} - {{option.label}}
|
||||
</template>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Kostenstelle"
|
||||
class=" mb-3"
|
||||
>
|
||||
<InputGroup class="w-full">
|
||||
<USelectMenu
|
||||
:options="costcentres"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['label']"
|
||||
searchable-placeholder="Suche..."
|
||||
v-model="item.costCentre"
|
||||
class="flex-auto"
|
||||
>
|
||||
<template #label>
|
||||
{{costcentres.find(i => i.id === item.costCentre) ? costcentres.find(i => i.id === item.costCentre).name : "Keine Kostenstelle ausgewählt" }}
|
||||
</template>
|
||||
<template #option="{option}">
|
||||
<span v-if="option.vehicle">{{option.number}} - Fahrzeug - {{option.name}}</span>
|
||||
<span v-else-if="option.project">{{option.number}} - Projekt - {{option.name}}</span>
|
||||
<span v-else-if="option.inventoryitem">{{option.number}} - Inventarartikel - {{option.name}}</span>
|
||||
<span v-else>{{option.number}} - {{option.name}}</span>
|
||||
</template>
|
||||
|
||||
</USelectMenu>
|
||||
<UButton
|
||||
variant="outline"
|
||||
color="rose"
|
||||
v-if="item.costCentre"
|
||||
icon="i-heroicons-x-mark"
|
||||
@click="item.costCentre = null"
|
||||
/>
|
||||
<EntityModalButtons
|
||||
type="costcentres"
|
||||
:id="item.costCentre"
|
||||
@return-data="(data) => setCostCentre(item,data)"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
|
||||
|
||||
<InputGroup>
|
||||
<UFormGroup
|
||||
v-if="useNetMode"
|
||||
label="Gesamtbetrag exkl. Steuer in EUR"
|
||||
class="flex-auto truncate"
|
||||
:help="item.taxType !== null ? `Betrag inkl. Steuern: ${String(Number(item.amountNet + item.amountTax).toFixed(2)).replace('.',',')} €` : 'Zuerst Steuertyp festlegen' "
|
||||
|
||||
>
|
||||
<UInput
|
||||
type="number"
|
||||
step="0.01"
|
||||
v-model="item.amountNet"
|
||||
:color="!item.amountNet ? 'rose' : 'primary'"
|
||||
:disabled="item.taxType === null"
|
||||
@keyup="item.amountTax = Number((item.amountNet * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
||||
item.amountGross = Number(item.amountNet) + Number(item.amountTax)"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
</template>
|
||||
</UInput>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
v-else
|
||||
label="Gesamtbetrag inkl. Steuer in EUR"
|
||||
class="flex-auto"
|
||||
:help="item.taxType !== null ? `Betrag exkl. Steuern: ${item.amountNet ? String(item.amountNet.toFixed(2)).replace('.',',') : '0,00'} €` : 'Zuerst Steuertyp festlegen' "
|
||||
|
||||
>
|
||||
<UInput
|
||||
type="number"
|
||||
step="0.01"
|
||||
:disabled="item.taxType === null"
|
||||
v-model="item.amountGross"
|
||||
:color="!item.amountGross ? 'rose' : 'primary'"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
@keyup="item.amountNet = Number((item.amountGross / (1 + Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
||||
item.amountTax = Number((item.amountGross - item.amountNet).toFixed(2))"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Umsatzsteuer"
|
||||
class="w-32"
|
||||
:help="`Betrag: ${item.amountTax ? String(item.amountTax).replace('.',',') : '0,00'} €`"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="taxOptions"
|
||||
v-model="item.taxType"
|
||||
value-attribute="key"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
option-attribute="label"
|
||||
@change="item.amountNet = Number((item.amountGross / (1 + Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
|
||||
item.amountTax = Number(((item.amountNet ? item.amountNet : 0) * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2))"
|
||||
>
|
||||
<template #label>
|
||||
<span class="truncate">{{taxOptions.find(i => i.key === item.taxType) ? taxOptions.find(i => i.key === item.taxType).label : ""}}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
<UButton
|
||||
class="mt-3"
|
||||
@click="itemInfo.accounts = [...itemInfo.accounts.slice(0,index+1),{account:null, amountNet: null, amountTax:null, taxType: '19'} , ...itemInfo.accounts.slice(index+1)]"
|
||||
>
|
||||
Betrag aufteilen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="index !== 0"
|
||||
class="mt-3"
|
||||
variant="ghost"
|
||||
color="rose"
|
||||
@click="itemInfo.accounts = itemInfo.accounts.filter((account,itemIndex) => itemIndex !== index)"
|
||||
>
|
||||
Position entfernen
|
||||
</UButton>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.documentPreview {
|
||||
aspect-ratio: 1 / 1.414;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.scrollContainer {
|
||||
overflow-y: scroll;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
height: 75vh;
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.scrollContainer::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.lineItemRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.workingContainer {
|
||||
height: 80vh;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,6 @@
|
||||
import InputGroup from "~/components/InputGroup.vue";
|
||||
import dayjs from "dayjs";
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ const type = "incominginvoices"
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
|
||||
const setupPage = async () => {
|
||||
//items.value = await useSupabaseSelect("incominginvoices","*, vendor(id,name), statementallocations(id,amount)","created_at",false)
|
||||
items.value = await useEntities(type).select("*, vendor(id,name), statementallocations(id,amount)",sort.value.column,sort.value.direction === "asc")
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import dayjs from "dayjs";
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -43,7 +42,6 @@ const loading = ref(true)
|
||||
|
||||
const setupPage = async () => {
|
||||
if((mode.value === "show") && route.params.id){
|
||||
//itemInfo.value = await useSupabaseSelectSingle("incominginvoices",route.params.id,"*, files(*), vendor(*)")
|
||||
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id,"*, files(*), vendor(*)")
|
||||
if(process.dev) console.log(itemInfo.value)
|
||||
currentDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
||||
|
||||
@@ -66,23 +66,8 @@ definePageMeta({
|
||||
middleware: 'redirect-to-mobile-index'
|
||||
})
|
||||
|
||||
|
||||
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
|
||||
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
|
||||
const { isNotificationsSlideoverOpen } = useDashboard()
|
||||
|
||||
|
||||
|
||||
|
||||
const user = useSupabaseUser()
|
||||
|
||||
const setup = async () => {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const mode = ref("incoming")
|
||||
const toast = useToast()
|
||||
|
||||
const inventoryChangeData = ref({
|
||||
productId: null,
|
||||
sourceSpaceId: null,
|
||||
sourceProjectId: null,
|
||||
destinationSpaceId: null,
|
||||
destinationProjectId: null,
|
||||
quantity: 1,
|
||||
serials: []
|
||||
})
|
||||
|
||||
const resetInput = () => {
|
||||
inventoryChangeData.value = {
|
||||
productId: null,
|
||||
sourceSpaceId: null,
|
||||
sourceProjectId: null,
|
||||
destinationSpaceId: null,
|
||||
destinationProjectId: null,
|
||||
quantity: 1
|
||||
}
|
||||
}
|
||||
|
||||
const createMovement = async () => {
|
||||
|
||||
let movements = []
|
||||
|
||||
if(mode.value === 'incoming'){
|
||||
|
||||
let movement = {
|
||||
productId: inventoryChangeData.value.productId,
|
||||
spaceId: inventoryChangeData.value.destinationSpaceId,
|
||||
projectId: inventoryChangeData.value.destinationProjectId,
|
||||
quantity: inventoryChangeData.value.quantity,
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
|
||||
movements.push(movement)
|
||||
|
||||
/*const {error} = await supabase
|
||||
.from("movements")
|
||||
.insert([inventoryChangeData.value])
|
||||
.select()
|
||||
if(error) console.log(error)*/
|
||||
|
||||
|
||||
} else if (mode.value === 'outgoing'){
|
||||
|
||||
let movement = {
|
||||
productId: inventoryChangeData.value.productId,
|
||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
||||
projectId: inventoryChangeData.value.sourceProjectId,
|
||||
quantity: inventoryChangeData.value.quantity * -1,
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
|
||||
movements.push(movement)
|
||||
} else if (mode.value === 'change'){
|
||||
let outMovement = {
|
||||
productId: inventoryChangeData.value.productId,
|
||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
||||
projectId: inventoryChangeData.value.sourceProjectId,
|
||||
quantity: inventoryChangeData.value.quantity * -1,
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
let inMovement = {
|
||||
productId: inventoryChangeData.value.productId,
|
||||
spaceId: inventoryChangeData.value.destinationSpaceId,
|
||||
projectId: inventoryChangeData.value.destinationProjectId,
|
||||
quantity: inventoryChangeData.value.quantity,
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
|
||||
movements.push(outMovement)
|
||||
movements.push(inMovement)
|
||||
}
|
||||
|
||||
console.log(movements)
|
||||
|
||||
const {error} = await supabase
|
||||
.from("movements")
|
||||
.insert(movements)
|
||||
.select()
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
resetInput()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
defineShortcuts({
|
||||
meta_enter: {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
createMovement()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function checkProductId(productId) {
|
||||
return dataStore.products.filter(product =>product.id === productId).length > 0;
|
||||
}
|
||||
function checkSpaceId(spaceId) {
|
||||
return dataStore.spaces.filter(space => space.id === spaceId).length > 0;
|
||||
}
|
||||
|
||||
function checkProjectId(projectId) {
|
||||
return dataStore.projects.some(i => i.id === projectId)
|
||||
}
|
||||
|
||||
function changeFocusToSpaceId() {
|
||||
document.getElementById('spaceIdInput').focus()
|
||||
}
|
||||
|
||||
function changeFocusToQuantity() {
|
||||
document.getElementById('quantityInput').focus()
|
||||
}
|
||||
|
||||
function changeFocusToBarcode() {
|
||||
document.getElementById('barcodeInput').focus()
|
||||
}
|
||||
|
||||
|
||||
const findProductByBarcodeOrEAN = (input) => {
|
||||
return dataStore.products.find(i => i.barcode === input || i.ean === input || i.articleNumber === input)
|
||||
}
|
||||
|
||||
const findSpaceBySpaceNumber = (input) => {
|
||||
return dataStore.spaces.find(i => i.spaceNumber === input)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const barcodeInput = ref("")
|
||||
const showBarcodeTip = ref(true)
|
||||
const serialInput = ref("")
|
||||
|
||||
const processBarcodeInput = () => {
|
||||
if(findProductByBarcodeOrEAN(barcodeInput.value) && !findSpaceBySpaceNumber(barcodeInput.value)){
|
||||
//Set Product
|
||||
|
||||
inventoryChangeData.value.productId = findProductByBarcodeOrEAN(barcodeInput.value).id
|
||||
} else if (!findProductByBarcodeOrEAN(barcodeInput.value) && findSpaceBySpaceNumber(barcodeInput.value)){
|
||||
//Set Space
|
||||
|
||||
if(mode.value === 'incoming'){
|
||||
inventoryChangeData.value.destinationSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
||||
} else if(mode.value === 'outgoing') {
|
||||
inventoryChangeData.value.sourceSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
||||
} else if(mode.value === 'change') {
|
||||
if(!inventoryChangeData.value.sourceSpaceId){
|
||||
inventoryChangeData.value.sourceSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
||||
} else {
|
||||
inventoryChangeData.value.destinationSpaceId = findSpaceBySpaceNumber(barcodeInput.value).id
|
||||
}
|
||||
}
|
||||
//console.log(findSpaceBySpaceNumber(barcodeInput.value))
|
||||
}
|
||||
barcodeInput.value = ""
|
||||
//console.log(movementData.value)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
title="Lager Vorgänge"
|
||||
>
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="resetInput"
|
||||
class="mt-3"
|
||||
color="rose"
|
||||
variant="outline"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="createMovement"
|
||||
:disabled="mode === '' && checkSpaceId(inventoryChangeData.spaceId) && checkProductId(inventoryChangeData.productId)"
|
||||
class="mt-3"
|
||||
>
|
||||
Bestätigen
|
||||
</UButton>
|
||||
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<div class="w-80 mx-auto mt-5">
|
||||
<div class="flex flex-col">
|
||||
<UButton
|
||||
@click="mode = 'incoming'"
|
||||
class="my-2"
|
||||
:variant="mode === 'incoming' ? 'solid' : 'outline'"
|
||||
>Wareneingang</UButton>
|
||||
<UButton
|
||||
@click="mode = 'outgoing'"
|
||||
class="my-2"
|
||||
:variant="mode === 'outgoing' ? 'solid' : 'outline'"
|
||||
>Warenausgang</UButton>
|
||||
<UButton
|
||||
@click="mode = 'change'"
|
||||
class="my-2"
|
||||
:variant="mode === 'change' ? 'solid' : 'outline'"
|
||||
>Umlagern</UButton>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
title="Info"
|
||||
variant="outline"
|
||||
color="primary"
|
||||
v-if="showBarcodeTip"
|
||||
@close="showBarcodeTip = false"
|
||||
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }"
|
||||
description="Über die Barcode Eingabe können folgende Werte automatisch erkannt werden: Quelllagerplatz, Ziellagerplatz, Artikel (EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt."
|
||||
/>
|
||||
|
||||
<!-- <UTooltip
|
||||
text="Über die Barcode Eingabe könenn folgende Werte automatisch erkannt werden: Quell Lagerplatz, Ziellagerplatz, Artikel(EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt."
|
||||
>-->
|
||||
<UFormGroup
|
||||
label="Barcode:"
|
||||
class="mt-3"
|
||||
>
|
||||
<UInput
|
||||
@keyup.enter="processBarcodeInput"
|
||||
@focusout="processBarcodeInput"
|
||||
@input="processBarcodeInput"
|
||||
v-model="barcodeInput"
|
||||
id="barcodeInput"
|
||||
|
||||
/>
|
||||
|
||||
</UFormGroup>
|
||||
<!-- <template #text>
|
||||
<span class="text-wrap">Über die Barcode Eingabe könenn folgende Werte automatisch erkannt werden: Quell Lagerplatz, Ziellagerplatz, Artikel(EAN oder Barcode). Es wird immer zuerst der Quell- und anschließend der Ziellagerplatz ausgefüllt.</span>
|
||||
|
||||
</template>
|
||||
</UTooltip>-->
|
||||
|
||||
<UDivider
|
||||
class="mt-5 w-80"
|
||||
v-if="mode !== 'incoming'"
|
||||
/>
|
||||
|
||||
<UFormGroup
|
||||
label="Quell Lagerplatz:"
|
||||
class="mt-3 w-80"
|
||||
v-if="mode !== 'incoming' "
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.spaces"
|
||||
searchable
|
||||
option-attribute="spaceNumber"
|
||||
:color="checkSpaceId(inventoryChangeData.sourceSpaceId) ? 'primary' : 'rose'"
|
||||
v-model="inventoryChangeData.sourceSpaceId"
|
||||
@change="inventoryChangeData.sourceProjectId = null"
|
||||
value-attribute="id"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.spaces.find(space => space.id === inventoryChangeData.sourceSpaceId) ? dataStore.spaces.find(space => space.id === inventoryChangeData.sourceSpaceId).description : "Kein Lagerplatz ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Quell Projekt:"
|
||||
class="mt-3 w-80"
|
||||
v-if="mode !== 'incoming' "
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.projects"
|
||||
searchable
|
||||
option-attribute="name"
|
||||
:color="checkProjectId(inventoryChangeData.sourceProjectId) ? 'primary' : 'rose'"
|
||||
v-model="inventoryChangeData.sourceProjectId"
|
||||
@change="inventoryChangeData.sourceSpaceId = null"
|
||||
value-attribute="id"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getProjectById(inventoryChangeData.sourceProjectId) ? dataStore.getProjectById(inventoryChangeData.sourceProjectId).name : "Kein Projekt ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UDivider
|
||||
class="mt-5 w-80"
|
||||
/>
|
||||
|
||||
<UFormGroup
|
||||
label="Artikel:"
|
||||
class="mt-3 w-80"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.products"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
variant="outline"
|
||||
searchable
|
||||
:search-attributes="['name','ean', 'barcode']"
|
||||
:color="checkProductId(inventoryChangeData.productId) ? 'primary' : 'rose'"
|
||||
v-model="inventoryChangeData.productId"
|
||||
v-on:select="changeFocusToSpaceId"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.products.find(product => product.id === inventoryChangeData.productId) ? dataStore.products.find(product => product.id === inventoryChangeData.productId).name : "Bitte Artikel auswählen"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UDivider
|
||||
class="mt-5 w-80"
|
||||
v-if="mode !== 'outgoing'"
|
||||
/>
|
||||
|
||||
<UFormGroup
|
||||
label="Ziel Lagerplatz:"
|
||||
class="mt-3 w-80"
|
||||
v-if="mode !== 'outgoing'"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.spaces"
|
||||
searchable
|
||||
option-attribute="spaceNumber"
|
||||
:color="checkSpaceId(inventoryChangeData.destinationSpaceId) ? 'primary' : 'rose'"
|
||||
v-model="inventoryChangeData.destinationSpaceId"
|
||||
@change="inventoryChangeData.destinationProjectId = null"
|
||||
value-attribute="id"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.spaces.find(space => space.id === inventoryChangeData.destinationSpaceId) ? dataStore.spaces.find(space => space.id === inventoryChangeData.destinationSpaceId).description : "Kein Lagerplatz ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Ziel Projekt:"
|
||||
class="mt-3 w-80"
|
||||
v-if="mode !== 'outgoing'"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.projects"
|
||||
searchable
|
||||
option-attribute="name"
|
||||
:color="checkProjectId(inventoryChangeData.destinationProjectId) ? 'primary' : 'rose'"
|
||||
v-model="inventoryChangeData.destinationProjectId"
|
||||
value-attribute="id"
|
||||
@change="inventoryChangeData.destinationSpaceId = null"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getProjectById(inventoryChangeData.destinationProjectId) ? dataStore.getProjectById(inventoryChangeData.destinationProjectId).name : "Kein Projekt ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UDivider
|
||||
class="mt-5 w-80"
|
||||
/>
|
||||
|
||||
<UFormGroup
|
||||
label="Anzahl:"
|
||||
class="mt-3 w-80"
|
||||
>
|
||||
<UInput
|
||||
variant="outline"
|
||||
color="primary"
|
||||
placeholder="Anzahl"
|
||||
v-model="inventoryChangeData.quantity"
|
||||
type="number"
|
||||
id="quantityInput"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Seriennummern:"
|
||||
class="mt-3 w-80"
|
||||
>
|
||||
<InputGroup class="w-full">
|
||||
<UInput
|
||||
variant="outline"
|
||||
color="primary"
|
||||
placeholder="Seriennummern"
|
||||
v-model="serialInput"
|
||||
/>
|
||||
<UButton
|
||||
@click="inventoryChangeData.serials.push(serialInput)"
|
||||
>
|
||||
+
|
||||
</UButton>
|
||||
</InputGroup>
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
<ul>
|
||||
<li v-for="serial in inventoryChangeData.serials">{{serial}}</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,150 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Bestände" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/products/create`)">+ Artikel</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/products/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel anzuzeigen' }"
|
||||
>
|
||||
<template #name-data="{row}">
|
||||
<span
|
||||
v-if="row === filteredRows[selectedItem]"
|
||||
class="text-primary-500 font-bold">{{row.name}}</span>
|
||||
<span v-else>
|
||||
{{row.name}}
|
||||
</span>
|
||||
|
||||
</template>
|
||||
<template #stock-data="{row}">
|
||||
{{`${dataStore.getStockByProductId(row.id)} ${(dataStore.units.find(unit => unit.id === row.unit) ? dataStore.units.find(unit => unit.id === row.unit).name : "")}`}}
|
||||
</template>
|
||||
|
||||
</UTable>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/products/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/products/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("products","*")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "stock",
|
||||
label: "Bestand"
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
sortable: true
|
||||
},
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
const templateTags = computed(() => {
|
||||
let temp = []
|
||||
|
||||
dataStore.products.forEach(row => {
|
||||
row.tags.forEach(tag => {
|
||||
if(!temp.includes(tag)) temp.push(tag)
|
||||
})
|
||||
})
|
||||
|
||||
return temp
|
||||
|
||||
})
|
||||
const selectedTags = ref(templateTags.value)
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
let temp = items.value.filter(i => i.tags.some(x => selectedTags.value.includes(x)) || i.tags.length === 0)
|
||||
|
||||
return useSearch(searchString.value, temp)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,8 +1,5 @@
|
||||
<script setup>
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import DocumentList from "~/components/DocumentList.vue";
|
||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,13 +30,7 @@ const checkBIC = async () => {
|
||||
|
||||
const generateLink = async (bankId) => {
|
||||
try {
|
||||
/*const {data,error} = await supabase.functions.invoke(`bankstatement_gateway`,{
|
||||
body: {
|
||||
method: "generateLink",
|
||||
institutionId: bankData.value.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
})*/
|
||||
|
||||
const link = await useFunctions().useBankingGenerateLink(bankId || bankData.value.id)
|
||||
|
||||
await navigateTo(link, {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
|
||||
const items = [{
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar >
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/settings/labels`)"
|
||||
>
|
||||
Labels
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
class="text-xl font-medium"
|
||||
>{{itemInfo.name ? `Label: ${itemInfo.name}` : (mode === 'create' ? 'Label erstellen' : 'Label bearbeiten')}}</h1>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UTabs
|
||||
:items="[{label: 'Informationen'}]"
|
||||
v-if="mode === 'show' && itemInfo"
|
||||
class="p-5"
|
||||
v-model="openTab"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<UCard class="mt-5">
|
||||
<div
|
||||
v-if="item.label === 'Informationen'"
|
||||
class="flex flex-row"
|
||||
>
|
||||
<div class="w-1/2 mr-5">
|
||||
<UDivider>Allgemeines</UDivider>
|
||||
<Toolbar>
|
||||
<UButton @click="usePrintLabel('0dbe30f3-3008-4cde-8a7c-e785b1c22bfc','ZD411',useGenerateZPL(itemInfo.handlebarsZPL,{barcode:'XXX'}))">Test Druck</UButton>
|
||||
|
||||
</Toolbar>
|
||||
<p>Name: {{itemInfo.name}}</p>
|
||||
<p>Breite in Zoll: {{itemInfo.widthInch}}"</p>
|
||||
<p>Höhe in Zoll: {{itemInfo.heightInch}}"</p>
|
||||
<p>ZPL:</p>
|
||||
<pre>{{itemInfo.handlebarsZPL}}</pre>
|
||||
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UDivider>Vorschau</UDivider>
|
||||
<img
|
||||
class="mx-auto mt-5"
|
||||
v-if="demoZPL"
|
||||
:src="`https://api.labelary.com/v1/printers/8dpmm/labels/${itemInfo.widthInch}x${itemInfo.heightInch}/0/${demoZPL}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</UCard>
|
||||
</template>
|
||||
</UTabs>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push("/settings/labels")
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < 3) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const mode = useRoute().params.mode
|
||||
const openTab = ref(0)
|
||||
|
||||
const itemInfo = ref({})
|
||||
|
||||
const setupPage = async () => {
|
||||
itemInfo.value = await useSupabaseSelectSingle("printLabels",useRoute().params.id,'*')
|
||||
renderDemoZPL()
|
||||
}
|
||||
|
||||
const demoZPL = ref("")
|
||||
const renderDemoZPL = () => {
|
||||
let template = Handlebars.compile(itemInfo.value.handlebarsZPL)
|
||||
|
||||
demoZPL.value = template({barcode: "XXX"})
|
||||
}
|
||||
|
||||
const printLabel = async () => {
|
||||
await supabase.from("printJobs").insert({
|
||||
tenant: profileStore.currentTenant,
|
||||
rawContent: useGenerateZPL(itemInfo.value.handlebarsZPL,{barcode:"XXX"}),
|
||||
printerName: "ZD411",
|
||||
printServer: "0dbe30f3-3008-4cde-8a7c-e785b1c22bfc"
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
setupPage()
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
img {
|
||||
border: 1px solid black
|
||||
}
|
||||
</style>
|
||||
@@ -1,111 +0,0 @@
|
||||
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
title="Labels"
|
||||
>
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/settings/labels/create`)" disabled>+ Label</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<!-- <UDashboardToolbar>
|
||||
|
||||
</UDashboardToolbar>-->
|
||||
|
||||
<UTable
|
||||
:rows="items"
|
||||
:columns="columns"
|
||||
@select="(i) => router.push(`/settings/labels/show/${i.id}`)"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel anzuzeigen' }"
|
||||
>
|
||||
<template #name-data="{row}">
|
||||
<span
|
||||
v-if="row === filteredRows[selectedItem]"
|
||||
class="text-primary-500 font-bold">{{row.name}}</span>
|
||||
<span v-else>
|
||||
{{row.name}}
|
||||
</span>
|
||||
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/settings/labels/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/settings/labels/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("printLabels","*")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const templateColumns = [{key: 'name',label:'Name'},{key: 'widthInch',label:'Breite in Zoll'},{key: 'heightInch',label:'Höhe in Zoll'}]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
|
||||
return useSearch(searchString.value, items.value)
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,78 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const data = await supabase.from("profiles").select('* , tenants (id, name)')
|
||||
console.log(data)
|
||||
|
||||
let rights = {
|
||||
createUser: {label: "Benutzer erstellen"},
|
||||
modifyUser: {label: "Benutzer bearbeiten"},
|
||||
deactivateUser: {label: "Benutzer sperren"},
|
||||
createProject: {label: "Projekt erstellen"},
|
||||
viewOwnProjects: {label: "Eigene Projekte sehen"},
|
||||
viewAllProjects: {label: "Alle Projekte sehen"},
|
||||
createTask: {label: "Aufgabe erstellen"},
|
||||
viewOwnTasks: {label:"Eigene Aufgaben sehen"},
|
||||
viewAllTasks: {label: "Alle Aufgaben sehen"},
|
||||
trackOwnTime: {label:"Eigene Zeite erfassen"},
|
||||
createOwnTime: {label:"Eigene Zeiten erstellen"},
|
||||
createTime: {label:"Zeiten erstellen"},
|
||||
viewOwnTimes: {label:"Eigene Zeiten anzeigen"},
|
||||
viewTimes: {label:"Zeiten anzeigen"},
|
||||
}
|
||||
|
||||
let roles = [
|
||||
{
|
||||
key: "tenantAdmin",
|
||||
label: "Firmenadministrator",
|
||||
rights: [
|
||||
...Object.keys(rights)
|
||||
]
|
||||
},
|
||||
{
|
||||
key:"worker",
|
||||
label: "Monteur",
|
||||
rights: [
|
||||
"viewOwnProjects",
|
||||
"createTasks",
|
||||
"viewOwnTasks"
|
||||
]
|
||||
},
|
||||
{
|
||||
key:"manager",
|
||||
label: "Vorarbeiter",
|
||||
rights: [
|
||||
"createProjects",
|
||||
"viewOwnProjects",
|
||||
"createTasks",
|
||||
"viewOwnTasks",
|
||||
]
|
||||
},
|
||||
{
|
||||
key:"booker",
|
||||
label: "Buchhalter",
|
||||
rights: [
|
||||
"createTasks",
|
||||
"viewOwnTasks",
|
||||
"createTime",
|
||||
"viewAllTimes"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -6,12 +6,13 @@ import dayjs from "dayjs";
|
||||
const supabase = useSupabaseClient()
|
||||
const profileStore = useProfileStore()
|
||||
const toast = useToast()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const itemInfo = ref({})
|
||||
const loaded = ref(false)
|
||||
|
||||
const setup = async () => {
|
||||
itemInfo.value = (await supabase.from("tickets").select("*, ticketmessages(*, profile(fullName, tenant, id)), created_by(*)").eq("id",useRoute().params.id).single()).data
|
||||
itemInfo.value = await useEntities("tickets").selectSingle(useRoute().params.id,"*, ticketmessages(*), created_by(*)")
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
@@ -20,85 +21,16 @@ setup()
|
||||
const messageContent = ref("")
|
||||
|
||||
const addMessage = async () => {
|
||||
const {data,error} = await supabase.from("ticketmessages").insert({
|
||||
profile: profileStore.activeProfile.id,
|
||||
content: messageContent.value,
|
||||
ticket: itemInfo.value.id,
|
||||
internal: false,
|
||||
type: "Nachricht"
|
||||
}).select().single()
|
||||
|
||||
if(error) {
|
||||
toast.add({title: "Erstellen fehlgeschlagen", color: "rose"})
|
||||
} else {
|
||||
toast.add({title: "Erstellen erfolgreich"})
|
||||
messageContent.value=""
|
||||
setup()
|
||||
if(profileStore.currentTenant !== 5) {
|
||||
await useFunctions().useSendTelegramNotification(`Neue Nachricht im Ticket ${useRoute().params.id} von ${profileStore.activeProfile.fullName}: ${data.content}`)
|
||||
} else if(profileStore.activeProfile.id !== itemInfo.value.created_by) {
|
||||
|
||||
let notification = {
|
||||
tenant: itemInfo.value.tenant,
|
||||
profile: itemInfo.value.created_by.id,
|
||||
initiatingProfile: profileStore.activeProfile.id,
|
||||
title: `Sie haben eine neue Nachricht von ${profileStore.activeProfile.fullName} im Ticket ${itemInfo.value.title}`,
|
||||
link: `/support/${itemInfo.value.id}`,
|
||||
message: data.content
|
||||
}
|
||||
|
||||
console.log(notification)
|
||||
|
||||
|
||||
const {error} = await supabase.from("notifications").insert(notification)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const showAddEntryModal = ref(false)
|
||||
const addEntryData = ref({})
|
||||
const addEntry = async () => {
|
||||
const {data,error} = await supabase.from("ticketmessages").insert({
|
||||
profile: profileStore.activeProfile.id,
|
||||
content: addEntryData.value.content,
|
||||
const res = await useEntities("ticketmessages").create({
|
||||
auth_user: auth.user.user_id,
|
||||
content: messageContent.value,
|
||||
ticket: itemInfo.value.id,
|
||||
internal: addEntryData.value.internal,
|
||||
type: addEntryData.value.type
|
||||
}).select().single()
|
||||
|
||||
if(error) {
|
||||
toast.add({title: "Erstellen fehlgeschlagen", color: "rose"})
|
||||
} else {
|
||||
toast.add({title: "Erstellen erfolgreich"})
|
||||
addEntryData.value = {}
|
||||
setup()
|
||||
showAddEntryModal.value = false
|
||||
|
||||
}
|
||||
internal: false,
|
||||
type: "Nachricht"
|
||||
})
|
||||
}
|
||||
|
||||
const closeTicket = async () => {
|
||||
const {data, error} = await supabase.from("tickets").update({status: "Geschlossen"}).eq("id",useRoute().params.id).single()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
console.log(data)
|
||||
|
||||
addEntryData.value.type = "Notiz"
|
||||
addEntryData.value.internal = false
|
||||
addEntryData.value.content = `Ticket durch ${profileStore.activeProfile.fullName} geschlossen`
|
||||
|
||||
addEntry()
|
||||
|
||||
}
|
||||
|
||||
setup()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -120,7 +52,7 @@ const closeTicket = async () => {
|
||||
{{itemInfo.title}}
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
<!-- <UButton
|
||||
v-if="profileStore.currentTenant === 5"
|
||||
variant="outline"
|
||||
@click="closeTicket"
|
||||
@@ -171,45 +103,14 @@ const closeTicket = async () => {
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</UModal>-->
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent v-if="loaded">
|
||||
<div
|
||||
v-if="profileStore.currentTenant === 5"
|
||||
v-for="item in itemInfo.ticketmessages"
|
||||
class="mb-3 flex flex-row p-5"
|
||||
:style="item.internal ? 'border: 1px solid red; border-radius: 15px' : item.profile.tenant === 5 ? 'border: 1px solid #69c350; border-radius: 15px' : 'border: 1px solid #fff; border-radius: 15px'"
|
||||
>
|
||||
<UAvatar :alt="item.profile.fullName" class="mr-3"/>
|
||||
<div>
|
||||
<p class="text-xl">{{item.type}} - {{item.profile.fullName}}</p>
|
||||
<p v-html="item.content"></p>
|
||||
<p class="mt-1 text-gray-600 dark:text-gray-400">{{dayjs(item.created_at).format("DD.MM.YYYY HH:mm")}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- <UAlert
|
||||
v-if="profileStore.currentTenant === 5"
|
||||
v-for="item in itemInfo.ticketmessages"
|
||||
:avatar="{ alt: item.profile.fullName}"
|
||||
:title="`${item.type} - ${item.profile.fullName}`"
|
||||
class="mb-3"
|
||||
:color="item.internal ? 'rose' : item.profile.tenant === 5 ? 'primary' : 'white'"
|
||||
variant="outline"
|
||||
>
|
||||
<template #description>
|
||||
<span v-html="item.content"></span>
|
||||
<p class="mt-1 text-gray-600 dark:text-gray-400">{{dayjs(item.created_at).format("DD.MM.YYYY HH:mm")}}</p>
|
||||
</template>
|
||||
</UAlert>-->
|
||||
<UAlert
|
||||
v-else
|
||||
v-for="item in itemInfo.ticketmessages.filter(i => !i.internal)"
|
||||
:avatar="{ alt: item.profile.fullName}"
|
||||
:title="`${item.type} - ${item.profile.fullName}`"
|
||||
:title="`${item.type}`"
|
||||
class="mb-3"
|
||||
:color="item.profile.tenant === 5 ? 'primary' : 'white'"
|
||||
variant="outline"
|
||||
|
||||
@@ -1,42 +1,30 @@
|
||||
<script setup>
|
||||
|
||||
import {useFunctions} from "~/composables/useFunctions.js";
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const profileStore = useProfileStore()
|
||||
const router = useRouter()
|
||||
|
||||
const itemInfo = ref({})
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
|
||||
const createTicket = async () => {
|
||||
|
||||
const {data:ticketData,error:ticketError} = await supabase.from("tickets").insert({
|
||||
const ticketRes = await useEntities("tickets").create({
|
||||
title: itemInfo.value.title,
|
||||
created_by: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}).select().single()
|
||||
})
|
||||
|
||||
console.log(ticketRes)
|
||||
|
||||
const ticketMsgRes = await useEntities("ticketmessages").create({
|
||||
ticket: ticketRes.id,
|
||||
created_by: auth.user.user_id,
|
||||
content: itemInfo.value.content,
|
||||
internal: false
|
||||
})
|
||||
|
||||
await router.push(`/support/${ticketRes.id}`)
|
||||
|
||||
|
||||
if(ticketError) {
|
||||
console.error(ticketError)
|
||||
} else {
|
||||
console.log(ticketData)
|
||||
const {data:messageData,error:messageError} = await supabase.from("ticketmessages").insert({
|
||||
ticket: ticketData.id,
|
||||
profile: profileStore.activeProfile.id,
|
||||
content: itemInfo.value.content,
|
||||
internal: false
|
||||
})
|
||||
|
||||
if(messageError) {
|
||||
console.log(messageError)
|
||||
} else {
|
||||
console.log(ticketData)
|
||||
//useFunctions().useSendTelegramNotification(`Ticket von ${profileStore.activeProfile.fullName} erstellt : ${itemInfo.value.content}`)
|
||||
router.push(`/support/${ticketData.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
const supabase = useSupabaseClient()
|
||||
const profileStore = useProfileStore()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -11,7 +10,7 @@ const showClosedTickets = ref(false)
|
||||
const selectedTenant = ref(null)
|
||||
|
||||
const setup = async () => {
|
||||
if(profileStore.currentTenant === 5) {
|
||||
/*if(profileStore.currentTenant === 5) {
|
||||
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*), tenant(*)").order("created_at", {ascending: false})).data
|
||||
} else {
|
||||
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*)").eq("tenant",profileStore.currentTenant).order("created_at", {ascending: false})).data
|
||||
@@ -19,7 +18,8 @@ const setup = async () => {
|
||||
|
||||
if(profileStore.currentTenant === 5) {
|
||||
tenants.value = (await supabase.from("tenants").select().order("id")).data
|
||||
}
|
||||
}*/
|
||||
tickets.value = await useEntities("tickets").select("*,created_by(*), ticketmessages(*)", "created_at", false)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push("/trackingTrips")
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < 3) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
const openTab = ref(0)
|
||||
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
name: "",
|
||||
type: "Geschäftlich",
|
||||
driver: null,
|
||||
active: true
|
||||
})
|
||||
const oldItemInfo = ref({})
|
||||
|
||||
const profiles = ref([])
|
||||
const projects = ref([])
|
||||
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
if(mode.value === "show" ){
|
||||
itemInfo.value = await useSupabaseSelectSingle("trackingtrips",route.params.id,"*, trackingDevice(*, vehicle(*))")
|
||||
} else if(mode.value === "edit") {
|
||||
itemInfo.value = await useSupabaseSelectSingle("trackingtrips",route.params.id,"*")
|
||||
|
||||
}
|
||||
|
||||
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
||||
|
||||
profiles.value = await useSupabaseSelect("profiles","*")
|
||||
projects.value = await useSupabaseSelect("projects","*")
|
||||
}
|
||||
|
||||
const cancelEditorCreate = () => {
|
||||
if(itemInfo.value) {
|
||||
router.push(`/trackingTrips/show/${itemInfo.value.id}`)
|
||||
} else {
|
||||
router.push(`/trackingTrips`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const getRowAmount = (row) => {
|
||||
let amount = 0
|
||||
|
||||
row.accounts.forEach(account => {
|
||||
amount += account.amountNet
|
||||
amount += account.amountTax
|
||||
})
|
||||
|
||||
return amount
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const zoom = ref(6)
|
||||
|
||||
</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(`/trackingTrips`)"
|
||||
>
|
||||
Fahrten
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
:class="['text-xl','font-medium' ]"
|
||||
><UIcon name="i-heroicons-lock-closed" v-if="itemInfo.fixed"/><UIcon name="i-heroicons-lock-open" v-else/> {{itemInfo ? `Fahrt vom: ${dayjs(itemInfo.startTime).format("DD.MM.YY HH:mm")} über ${(itemInfo.distance/1000).toFixed(2)} km` : '' }} </h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="cancelEditorCreate"
|
||||
color="red"
|
||||
variant="outline"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
variant="outline"
|
||||
color="primary"
|
||||
icon="i-mdi-content-save"
|
||||
@click="dataStore.updateItem('trackingtrips',itemInfo, oldItemInfo)"
|
||||
>
|
||||
Entwurf
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
icon="i-mdi-content-save"
|
||||
@click="dataStore.updateItem('trackingtrips',{...itemInfo, fixed:true}, oldItemInfo)"
|
||||
>
|
||||
Festschreiben
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show' && !itemInfo.fixed"
|
||||
@click="router.push(`/trackingTrips/edit/${itemInfo.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
|
||||
|
||||
<UTabs
|
||||
:items="[{label: 'Informationen'}]"
|
||||
v-if="mode === 'show' && itemInfo.id"
|
||||
class="p-5"
|
||||
v-model="openTab"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.label === 'Informationen'" class="flex flex-col mt-5">
|
||||
<div class="flex flex-row" style="height: 40vh">
|
||||
<div class="w-1/2 mr-5">
|
||||
<UCard>
|
||||
<table class="w-full">
|
||||
<tr>
|
||||
<td>Festgeschrieben:</td>
|
||||
<td>{{itemInfo.fixed ? "Ja" : "Nein"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fahrzeug:</td>
|
||||
<td><nuxt-link :to="`/vehicles/show/${itemInfo.trackingDevice.vehicle.id}`">{{itemInfo.trackingDevice.vehicle.licensePlate}}</nuxt-link></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Typ:</td>
|
||||
<td>{{itemInfo.type ? itemInfo.type : "-"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fahrer:</td>
|
||||
<td>{{profiles.find(i => i.id === itemInfo.driver) ? profiles.find(i => i.id === itemInfo.driver).fullName : "-"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Startzeit:</td>
|
||||
<td>{{dayjs(itemInfo.startTime).format("DD.MM.YY HH:mm")}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Endzeit:</td>
|
||||
<td>{{dayjs(itemInfo.endTime).format("DD.MM.YY HH:mm")}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Entfernung:</td>
|
||||
<td>{{(itemInfo.distance/1000).toFixed(2)}} km</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Beschreibung:</td>
|
||||
<td>{{itemInfo.description ? itemInfo.description : "-" }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</UCard>
|
||||
<UCard class="mt-5">
|
||||
<Map :markers="[[itemInfo.startLatitude, itemInfo.startLongitude],[itemInfo.endLatitude, itemInfo.endLongitude]]"
|
||||
:startMarker="[itemInfo.startLatitude, itemInfo.startLongitude]"
|
||||
:endMarker="[itemInfo.endLatitude, itemInfo.endLongitude]"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
</UCard>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UCard>
|
||||
<div style="height: 75vh">
|
||||
<HistoryDisplay
|
||||
type="trackingtrip"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
render-headline
|
||||
/>
|
||||
</div>
|
||||
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-40 mt-5">
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</UTabs>
|
||||
<UForm
|
||||
v-else-if="mode === 'edit' || mode === 'create'"
|
||||
class="p-5"
|
||||
>
|
||||
<UFormGroup
|
||||
label="Typ:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.type"
|
||||
:options="['Privat','Geschäftlich','Arbeitsweg']"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Fahrer:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.driver"
|
||||
:options="[{id: null, fullName: 'Kein Fahrer'},...profiles]"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
|
||||
>
|
||||
<template #label>
|
||||
{{profiles.find(profile => profile.id === itemInfo.driver) ? profiles.find(profile => profile.id === itemInfo.driver).fullName : 'Kein Fahrer ausgewählt'}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Projekt:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.project"
|
||||
:options="projects"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Beschreibung:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.description"
|
||||
rows="6"
|
||||
maxrows="6"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
vertical-align: top;
|
||||
padding-bottom: 0.15em;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,155 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Fahrten" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<!-- <UButton @click="router.push(`/vehicles/create`)">+ Fahrzeug</UButton>-->
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/trackingTrips/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Fahrten anzuzeigen' }"
|
||||
>
|
||||
<template #fixed-data="{row}">
|
||||
<UIcon name="i-heroicons-lock-closed" v-if="row.fixed"/>
|
||||
<div v-else></div>
|
||||
</template>
|
||||
<template #licensePlate-data="{row}">
|
||||
<span v-if="row === filteredRows[selectedItem]" class="font-bold text-primary-500">{{row.vehicle.licensePlate}}</span>
|
||||
<span v-else>{{row.vehicle.licensePlate}}</span>
|
||||
</template>
|
||||
<template #startTime-data="{row}">
|
||||
{{dayjs(row.startTime).format("DD.MM.YY HH:mm")}}
|
||||
</template>
|
||||
<template #endTime-data="{row}">
|
||||
{{dayjs(row.endTime).format("DD.MM.YY HH:mm")}}
|
||||
</template>
|
||||
<template #distance-data="{row}">
|
||||
{{row.distance > 0 ? (row.distance/1000).toFixed(2) : 0 }} km
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import dayjs from "dayjs";
|
||||
|
||||
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/trackingTrips/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("trackingtrips","*, trackingDevice(*), vehicle (*)","startTime",false)
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: 'fixed',
|
||||
label: ""
|
||||
},
|
||||
{
|
||||
key: 'licensePlate',
|
||||
label: "Kennzeichen:",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "Typ:",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "startTime",
|
||||
label: "Startzeit:",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "endTime",
|
||||
label: "Endzeit:",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "distance",
|
||||
label: "Entfernung:",
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
return useSearch(searchString.value, items.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,8 +1,10 @@
|
||||
import {Preferences} from "@capacitor/preferences";
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const api = $fetch.create({
|
||||
baseURL: /*"http://192.168.1.227:3100"*/ "https://backend.fedeo.io",
|
||||
baseURL: config.public.apiBase,/*"http://192.168.1.227:3100" "https://backend.fedeo.io"*/
|
||||
credentials: "include",
|
||||
async onRequest({options}) {
|
||||
// Token aus Cookie holen
|
||||
|
||||
145
stores/data.js
145
stores/data.js
@@ -3,7 +3,6 @@ import dayjs from "dayjs"
|
||||
//import {typeOf} from "uri-js/dist/esnext/util";
|
||||
import {useNumberRange} from "~/composables/useNumberRange.js";
|
||||
|
||||
//const supabase = createClient('https://uwppvcxflrcsibuzsbil.supabase.co','eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo')
|
||||
|
||||
import projecttype from "~/components/columnRenderings/projecttype.vue"
|
||||
import customer from "~/components/columnRenderings/customer.vue"
|
||||
@@ -49,7 +48,6 @@ import sepaDate from "~/components/columnRenderings/sepaDate.vue";
|
||||
// @ts-ignore
|
||||
export const useDataStore = defineStore('data', () => {
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const profileStore = useProfileStore()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
@@ -1566,6 +1564,18 @@ export const useDataStore = defineStore('data', () => {
|
||||
}
|
||||
]
|
||||
},
|
||||
tickets: {
|
||||
isArchivable: true,
|
||||
label: "Tickets",
|
||||
labelSingle: "Ticket",
|
||||
|
||||
},
|
||||
ticketmessages: {
|
||||
isArchivable: true,
|
||||
label: "Nachrichten",
|
||||
labelSingle: "Nachricht",
|
||||
|
||||
},
|
||||
files: {
|
||||
isArchivable: true,
|
||||
label: "Dateien",
|
||||
@@ -2593,136 +2603,7 @@ export const useDataStore = defineStore('data', () => {
|
||||
})
|
||||
|
||||
|
||||
async function createNewItem (dataType,data,noRedirect=false){
|
||||
if(typeof(data) === 'object') {
|
||||
data = {...data, tenant: profileStore.currentTenant}
|
||||
} else if(typeof(data) === 'array') {
|
||||
data.map(i => {
|
||||
return {
|
||||
...i,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
console.log(dataType)
|
||||
if(dataTypes[dataType].numberRangeHolder) {
|
||||
|
||||
if(!data[dataTypes[dataType].numberRangeHolder]) {
|
||||
data[dataTypes[dataType].numberRangeHolder] = await useFunctions().useNextNumber(dataType)
|
||||
}
|
||||
|
||||
|
||||
} else if(dataType === "createddocuments" && data.type !== "serialInvoices") {
|
||||
/*if(data.state !== "Entwurf") {
|
||||
console.log(data.type)
|
||||
|
||||
let type = ""
|
||||
if(data.type === "advanceInvoices"){
|
||||
type = "invoices"
|
||||
} else {
|
||||
type = data.type
|
||||
}
|
||||
|
||||
const numberRange = useNumberRange(type)
|
||||
data.documentNumber = await numberRange.useNextNumber()
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
const {data:supabaseData,error:supabaseError} = await supabase
|
||||
.from(dataType)
|
||||
.insert(data)
|
||||
.select()
|
||||
|
||||
if(supabaseError) {
|
||||
console.log(supabaseError)
|
||||
toast.add({title: "Es ist ein Fehler bei der Erstellung aufgetreten", color: "rose"})
|
||||
} else if (supabaseData) {
|
||||
console.log(supabaseData)
|
||||
|
||||
let returnData = supabaseData[0]
|
||||
|
||||
await generateHistoryItems(dataType, supabaseData[0])
|
||||
|
||||
/*if(!["statementallocations","absencerequests", "productcategories", "servicecategories", "projecttypes", "checks", "profiles","services", "inventoryitems", "inventoryitemgroups", "incominginvoices", "costcentres", "ownaccounts"].includes(dataType) ){
|
||||
await eval( dataType + '.value.push(' + JSON.stringify(...supabaseData) + ')')
|
||||
}*/
|
||||
|
||||
|
||||
toast.add({title: `${dataTypes[dataType].labelSingle} hinzugefügt`})
|
||||
if(dataTypes[dataType].redirect && !noRedirect) {
|
||||
if(dataTypes[dataType].isStandardEntity) {
|
||||
await router.push(dataTypes[dataType].redirectToList ? `/standardEntity/${dataType}` : `/standardEntity/${dataType}/show/${returnData.id}`)
|
||||
} else {
|
||||
await router.push(dataTypes[dataType].redirectToList ? `/${dataType}` : `/${dataType}/show/${returnData.id}`)
|
||||
}
|
||||
}
|
||||
modal.close()
|
||||
return supabaseData[0]
|
||||
}
|
||||
}
|
||||
|
||||
async function updateItem (dataType, data, oldData = null, noRedirect = false) {
|
||||
//console.log(dataType, data)
|
||||
//Temporary Fix TODO: Remove and build Solution
|
||||
data = JSON.parse(JSON.stringify(data))
|
||||
delete data.users
|
||||
|
||||
if(oldData) {
|
||||
oldData = JSON.parse(JSON.stringify(oldData))
|
||||
delete oldData.users
|
||||
}
|
||||
|
||||
const {tenants, ...newData} = data
|
||||
|
||||
/*if(dataType === "createddocuments" && data.type !== "serialInvoices") {
|
||||
if(data.state !== "Entwurf") {
|
||||
console.log(data.type)
|
||||
|
||||
let type = ""
|
||||
if(data.type === "advanceInvoices"){
|
||||
type = "invoices"
|
||||
} else {
|
||||
type = data.type
|
||||
}
|
||||
|
||||
const numberRange = useNumberRange(type)
|
||||
data.documentNumber = await numberRange.useNextNumber()
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
await generateHistoryItems(dataType,data,oldData)
|
||||
|
||||
const {data:supabaseData,error: supabaseError} = await supabase
|
||||
.from(dataType)
|
||||
.update(newData)
|
||||
.eq('id',newData.id)
|
||||
.select()
|
||||
|
||||
if(supabaseError) {
|
||||
console.log(supabaseError)
|
||||
toast.add({title: `Fehler beim Speichern`, color: 'rose'})
|
||||
} else if(supabaseData) {
|
||||
//await eval(dataType + '.value[' + dataType + '.value.findIndex(i => i.id === ' + JSON.stringify(data.id) + ')] = ' + JSON.stringify(supabaseData[0]))
|
||||
//if(dataType === 'profiles') await fetchProfiles()
|
||||
toast.add({title: `${dataTypes[dataType].labelSingle} gespeichert`})
|
||||
if(dataTypes[dataType].redirect && !noRedirect) {
|
||||
if(dataTypes[dataType].isStandardEntity) {
|
||||
await router.push(dataTypes[dataType].redirectToList ? `/standardEntity/${dataType}` : `/standardEntity/${dataType}/show/${data.id}`)
|
||||
} else {
|
||||
await router.push(dataTypes[dataType].redirectToList ? `/${dataType}` : `/${dataType}/show/${data.id}`)
|
||||
}
|
||||
}
|
||||
modal.close()
|
||||
return supabaseData[0]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2732,7 +2613,5 @@ export const useDataStore = defineStore('data', () => {
|
||||
return {
|
||||
dataTypes,
|
||||
documentTypesForCreation,
|
||||
createNewItem,
|
||||
updateItem,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user