Introduced Help Tickets
This commit is contained in:
@@ -19,6 +19,10 @@ const links = [{
|
|||||||
shortcuts.value = true
|
shortcuts.value = true
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
label: 'Tickets',
|
||||||
|
icon: 'i-heroicons-clipboard-document',
|
||||||
|
to: '/support',
|
||||||
|
}, {
|
||||||
label: 'Webseite',
|
label: 'Webseite',
|
||||||
icon: 'i-heroicons-globe-europe-africa',
|
icon: 'i-heroicons-globe-europe-africa',
|
||||||
to: 'https://fedeo.de',
|
to: 'https://fedeo.de',
|
||||||
@@ -173,21 +177,21 @@ const resetContactRequest = () => {
|
|||||||
<div v-else class="flex flex-col gap-y-3">
|
<div v-else class="flex flex-col gap-y-3">
|
||||||
<UButton v-for="(link, index) in links" :key="index" color="white" v-bind="link" />
|
<UButton v-for="(link, index) in links" :key="index" color="white" v-bind="link" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5" v-if="!loadingContactRequest">
|
<!-- <div class="mt-5" v-if="!loadingContactRequest">
|
||||||
<h1 class="font-semibold">Kontaktanfrage:</h1>
|
<h1 class="font-semibold">Kontaktanfrage:</h1>
|
||||||
<UForm
|
<UForm
|
||||||
class="p-3"
|
class="p-3"
|
||||||
@submit="addContactRequest"
|
@submit="addContactRequest"
|
||||||
@reset="resetContactRequest"
|
@reset="resetContactRequest"
|
||||||
>
|
>
|
||||||
<!-- <UFormGroup
|
<!– <UFormGroup
|
||||||
label="Art:"
|
label="Art:"
|
||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="['Hilfe','Software Problem / Bug','Funktionsanfrage','Kontakt','Sonstiges']"
|
:options="['Hilfe','Software Problem / Bug','Funktionsanfrage','Kontakt','Sonstiges']"
|
||||||
v-model="contactRequestData.contactType"
|
v-model="contactRequestData.contactType"
|
||||||
/>
|
/>
|
||||||
</UFormGroup>-->
|
</UFormGroup>–>
|
||||||
<UFormGroup
|
<UFormGroup
|
||||||
label="Titel:"
|
label="Titel:"
|
||||||
>
|
>
|
||||||
@@ -222,6 +226,6 @@ const resetContactRequest = () => {
|
|||||||
|
|
||||||
</UForm>
|
</UForm>
|
||||||
</div>
|
</div>
|
||||||
<UProgress class="mt-5" animation="carousel" v-else/>
|
<UProgress class="mt-5" animation="carousel" v-else/>-->
|
||||||
</UDashboardSlideover>
|
</UDashboardSlideover>
|
||||||
</template>
|
</template>
|
||||||
@@ -13,6 +13,11 @@ const links = computed(() => {
|
|||||||
icon: "i-heroicons-rectangle-stack",
|
icon: "i-heroicons-rectangle-stack",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
}] : [],
|
}] : [],
|
||||||
|
... profileStore.currentTenant === 5 ? [{
|
||||||
|
label: "Support Tickets",
|
||||||
|
to: "/support",
|
||||||
|
icon: "i-heroicons-rectangle-stack",
|
||||||
|
}] : [],
|
||||||
{
|
{
|
||||||
id: 'dashboard',
|
id: 'dashboard',
|
||||||
label: "Dashboard",
|
label: "Dashboard",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const baseURL = /*"http://localhost:3333"*/ "https://functions.fedeo.io"
|
const baseURL = "http://localhost:3333" //"https://functions.fedeo.io"
|
||||||
|
|
||||||
export const useFunctions = () => {
|
export const useFunctions = () => {
|
||||||
const supabase = useSupabaseClient()
|
const supabase = useSupabaseClient()
|
||||||
@@ -101,6 +101,21 @@ export const useFunctions = () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useSendTelegramNotification = async (message) => {
|
||||||
|
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||||
|
|
||||||
|
const {data} = await axios({
|
||||||
|
method: "POST",
|
||||||
|
url: `${baseURL}/functions/sendtelegramnotification`,
|
||||||
|
data: {
|
||||||
|
message: message
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const useBankingCheckInstitutions = async (bic) => {
|
const useBankingCheckInstitutions = async (bic) => {
|
||||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||||
|
|
||||||
@@ -131,5 +146,5 @@ export const useFunctions = () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {getWorkingTimesEvaluationData, useNextNumber, useCreateTicket, useBankingGenerateLink, useBankingCheckInstitutions, useBankingListRequisitions, useCreatePDF}
|
return {getWorkingTimesEvaluationData, useNextNumber, useCreateTicket, useBankingGenerateLink, useBankingCheckInstitutions, useBankingListRequisitions, useCreatePDF, useSendTelegramNotification}
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,11 @@
|
|||||||
>
|
>
|
||||||
<display-present-profiles/>
|
<display-present-profiles/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>
|
||||||
|
<UDashboardCard
|
||||||
|
class="w-1/3 h-fit mx-2 mt-3"
|
||||||
|
>
|
||||||
|
<display-running-time/>
|
||||||
|
</UDashboardCard>
|
||||||
|
|
||||||
|
|
||||||
<!-- <UDashboardCard
|
<!-- <UDashboardCard
|
||||||
@@ -29,6 +34,7 @@
|
|||||||
</UDashboardCard>-->
|
</UDashboardCard>-->
|
||||||
<UDashboardCard
|
<UDashboardCard
|
||||||
class="w-1/3 h-fit mx-2 mt-3"
|
class="w-1/3 h-fit mx-2 mt-3"
|
||||||
|
v-if="profileStore.ownTenant.features.accounting"
|
||||||
>
|
>
|
||||||
<display-open-balances/>
|
<display-open-balances/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>
|
||||||
@@ -38,11 +44,6 @@
|
|||||||
>
|
>
|
||||||
<display-projects-in-phases/>
|
<display-projects-in-phases/>
|
||||||
</UDashboardCard>
|
</UDashboardCard>
|
||||||
<UDashboardCard
|
|
||||||
class="w-1/3 h-fit mx-2 mt-3"
|
|
||||||
>
|
|
||||||
<display-running-time/>
|
|
||||||
</UDashboardCard>
|
|
||||||
</UDashboardPanelContent>
|
</UDashboardPanelContent>
|
||||||
</UDashboardPanel>
|
</UDashboardPanel>
|
||||||
</UDashboardPage>
|
</UDashboardPage>
|
||||||
|
|||||||
179
pages/support/[id].vue
Normal file
179
pages/support/[id].vue
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {useFunctions} from "~/composables/useFunctions.js";
|
||||||
|
|
||||||
|
const supabase = useSupabaseClient()
|
||||||
|
const profileStore = useProfileStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
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
|
||||||
|
loaded.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
await useFunctions().useSendTelegramNotification(`Neue Nachricht im Ticket ${useRoute().params.id} von ${profileStore.activeProfile.fullName}: ${data.content}`)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UDashboardNavbar title="Support Ticket">
|
||||||
|
<template #badge>
|
||||||
|
<UBadge
|
||||||
|
variant="outline"
|
||||||
|
v-if="itemInfo.status === 'Offen'"
|
||||||
|
color="yellow"
|
||||||
|
>{{itemInfo.status}}</UBadge>
|
||||||
|
<UBadge
|
||||||
|
variant="outline"
|
||||||
|
v-if="itemInfo.status === 'Geschlossen'"
|
||||||
|
color="primary"
|
||||||
|
>{{itemInfo.status}}</UBadge>
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<UButton
|
||||||
|
v-if="profileStore.currentTenant === 5"
|
||||||
|
variant="outline"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
Ticket Schließen
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
v-if="profileStore.currentTenant === 5"
|
||||||
|
variant="outline"
|
||||||
|
@click="showAddEntryModal = true"
|
||||||
|
>
|
||||||
|
+ Eintrag
|
||||||
|
</UButton>
|
||||||
|
<UModal v-model="showAddEntryModal">
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
Eintrag hinzufügen
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<UFormGroup
|
||||||
|
label="Intern:"
|
||||||
|
>
|
||||||
|
<UToggle
|
||||||
|
v-model="addEntryData.internal"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
<UFormGroup
|
||||||
|
label="Typ:"
|
||||||
|
>
|
||||||
|
<USelectMenu
|
||||||
|
v-model="addEntryData.type"
|
||||||
|
:options="['Nachricht','Notiz','Anruf','Externe Kommunikation']"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
<UFormGroup
|
||||||
|
label="Inhalt:"
|
||||||
|
>
|
||||||
|
<UTextarea
|
||||||
|
v-model="addEntryData.content"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<UButton
|
||||||
|
@click="addEntry"
|
||||||
|
>
|
||||||
|
Erstellen
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
</UModal>
|
||||||
|
</template>
|
||||||
|
</UDashboardNavbar>
|
||||||
|
<UDashboardPanelContent v-if="loaded">
|
||||||
|
<UAlert
|
||||||
|
v-if="profileStore.currentTenant === 5"
|
||||||
|
v-for="item in itemInfo.ticketmessages"
|
||||||
|
:description="item.content"
|
||||||
|
: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"
|
||||||
|
/>
|
||||||
|
<UAlert
|
||||||
|
v-else
|
||||||
|
v-for="item in itemInfo.ticketmessages.filter(i => !i.internal)"
|
||||||
|
:description="item.content"
|
||||||
|
:avatar="{ alt: item.profile.fullName}"
|
||||||
|
:title="item.profile.fullName"
|
||||||
|
class="mb-3"
|
||||||
|
:color="item.profile.tenant === 5 ? 'primary' : 'white'"
|
||||||
|
variant="outline"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<UInput
|
||||||
|
class="w-full mr-2"
|
||||||
|
placeholder="Neue Nachricht senden"
|
||||||
|
v-model="messageContent"
|
||||||
|
@keyup.enter="addMessage"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="addMessage"
|
||||||
|
>
|
||||||
|
Senden
|
||||||
|
</UButton>
|
||||||
|
</InputGroup>
|
||||||
|
|
||||||
|
</UDashboardPanelContent>
|
||||||
|
<UProgress animation="carousel" v-else class="w-3/4 mx-auto"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
73
pages/support/create.vue
Normal file
73
pages/support/create.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {useFunctions} from "~/composables/useFunctions.js";
|
||||||
|
|
||||||
|
const supabase = useSupabaseClient()
|
||||||
|
const profileStore = useProfileStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const itemInfo = ref({})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const createTicket = async () => {
|
||||||
|
|
||||||
|
const {data:ticketData,error:ticketError} = await supabase.from("tickets").insert({
|
||||||
|
title: itemInfo.value.title,
|
||||||
|
created_by: profileStore.activeProfile.id,
|
||||||
|
tenant: profileStore.currentTenant
|
||||||
|
}).select().single()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
router.push(`/support/${ticketData.id}`)
|
||||||
|
await useFunctions().useSendTelegramNotification(`Ticket von ${profileStore.activeProfile.fullName} erstellt : ${itemInfo.value.content}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UDashboardNavbar title="Neues Ticket erstellen">
|
||||||
|
<template #right>
|
||||||
|
<UButton
|
||||||
|
@click="createTicket"
|
||||||
|
>
|
||||||
|
Erstellen
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
</UDashboardNavbar>
|
||||||
|
<UForm class="w-2/3 mx-auto mt-5">
|
||||||
|
<UFormGroup
|
||||||
|
label="Titel:"
|
||||||
|
>
|
||||||
|
<UInput
|
||||||
|
v-model="itemInfo.title"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
<UFormGroup
|
||||||
|
label="Nachricht:"
|
||||||
|
>
|
||||||
|
<UTextarea
|
||||||
|
v-model="itemInfo.content"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
</UForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
56
pages/support/index.vue
Normal file
56
pages/support/index.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<script setup>
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
const supabase = useSupabaseClient()
|
||||||
|
const profileStore = useProfileStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const tickets = ref([])
|
||||||
|
|
||||||
|
const setup = async () => {
|
||||||
|
if(profileStore.currentTenant === 5) {
|
||||||
|
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*), tenant(*)")).data
|
||||||
|
} else {
|
||||||
|
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*)").eq("tenant",profileStore.currentTenant)).data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UDashboardNavbar
|
||||||
|
title="Suport Tickets"
|
||||||
|
>
|
||||||
|
<template #right>
|
||||||
|
<UButton
|
||||||
|
@click="router.push('/support/create')"
|
||||||
|
>
|
||||||
|
+ Ticket
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
</UDashboardNavbar>
|
||||||
|
<UTable
|
||||||
|
:rows="tickets"
|
||||||
|
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine Tickets anzuzeigen` }"
|
||||||
|
@select="(i) => router.push(`/support/${i.id}`)"
|
||||||
|
:columns="[{key:'created_at',label:'Datum'}, ...profileStore.currentTenant === 5 ? [{key:'tenant',label:'Tenant'}] : [],{key:'title',label:'Titel'},{key:'created_by',label:'Ersteller'},{key:'ticketmessages',label:'Nachrichten'}]"
|
||||||
|
>
|
||||||
|
<template #tenant-data="{ row }">
|
||||||
|
{{row.tenant.name}}
|
||||||
|
</template>
|
||||||
|
<template #created_by-data="{ row }">
|
||||||
|
{{row.created_by.fullName}}
|
||||||
|
</template>
|
||||||
|
<template #created_at-data="{ row }">
|
||||||
|
{{dayjs(row.created_at).format('DD.MM.YYYY HH:mm')}}
|
||||||
|
</template>
|
||||||
|
<template #ticketmessages-data="{ row }">
|
||||||
|
{{row.ticketmessages.length}}
|
||||||
|
</template>
|
||||||
|
</UTable>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user