Merge branch 'beta'

# Conflicts:
#	components/DocumentDisplay.vue
#	components/MainNav.vue
#	composables/useRole.js
#	composables/useSupabase.js
#	pages/createDocument/edit/[[id]].vue
#	pages/files/index.vue
#	pages/inventoryitems/index.vue
#	pages/services/[mode]/[[id]].vue
#	pages/vendors/[mode]/[[id]].vue
#	stores/data.js
This commit is contained in:
2025-01-20 11:05:22 +01:00
135 changed files with 7403 additions and 11455 deletions

View File

@@ -1,208 +0,0 @@
<script setup>
import dayjs from "dayjs";
import HistoryDisplay from "~/components/HistoryDisplay.vue";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
approved: "Offen"
})
const states = ["Offen","Genehmigt", "Abgelehnt"]
const absenceReasons = [
"Elternzeit",
"Kind krank - Kinderbetreuung",
"Krankheit",
"Krankheit 1 Tag (mit Attest)",
"Krankheit ab 2. Tag (mit Attest)",
"Mutterschutz",
"Sonderurlaub (bezahlt)",
"Überstundenausgleich",
"Unbezahlter Urlaub",
"Urlaub"
]
//Functions
const setupPage = () => {
if(mode.value === "show" || mode.value === "edit"){
itemInfo.value = dataStore.getAbsenceRequestById(Number(useRoute().params.id))
}
}
const editItem = async () => {
router.push(`/absenceRequests/edit/${itemInfo.value.id}`)
setupPage()
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/absenceRequests/show/${itemInfo.value.id}`)
} else {
router.push(`/absenceRequests/`)
}
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Abwesenheit erstellen' : 'Abwesenheit bearbeiten')">
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('absencerequests',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('absencerequests',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="editItem"
>
Bearbeiten
</UButton>
</template>
<template #badge v-if="itemInfo">
<UBadge
v-if="itemInfo.approved === 'Offen'"
color="blue"
>{{itemInfo.approved}}</UBadge>
<UBadge
v-else-if="itemInfo.approved === 'Genehmigt'"
color="primary"
>{{itemInfo.approved}}</UBadge>
<UBadge
v-else-if="itemInfo.approved === 'Abgelehnt'"
color="rose"
>{{itemInfo.approved}}</UBadge>
</template>
</UDashboardNavbar>
<UTabs
:items="[{label: 'Informationen'}, {label: 'Logbuch'}]"
v-if="itemInfo && mode == 'show'"
class="p-5"
>
<template #item="{item}">
<UCard class="mt-5">
<div v-if="item.label === 'Informationen'">
<div class="truncate">
<p>Mitarbeiter: {{dataStore.profiles.find(item => item.id === itemInfo.user) ? dataStore.profiles.find(item => item.id === itemInfo.user).fullName : ""}}</p>
<p>Start: {{dayjs(itemInfo.start).format("DD.MM.YYYY")}}</p>
<p>Ende: {{dayjs(itemInfo.end).format("DD.MM.YYYY")}}</p>
<p>Grund: {{itemInfo.reason}}</p>
<p>Notizen: {{itemInfo.notes}}</p>
</div>
</div>
<div v-else-if="item.label === 'Logbuch'">
<HistoryDisplay
type="absencerequest"
v-if="itemInfo"
:element-id="itemInfo.id"
/>
</div>
</UCard>
</template>
</UTabs>
<UForm v-else-if="mode == 'edit' || mode == 'create'" class="p-5" >
<UFormGroup
label="Status:"
>
<USelectMenu
v-model="itemInfo.approved"
:options="states"
/>
</UFormGroup>
<UFormGroup
label="Grund:"
>
<USelectMenu
v-model="itemInfo.reason"
:options="absenceReasons"
/>
</UFormGroup>
<UFormGroup
label="Mitarbeiter:"
>
<USelectMenu
v-model="itemInfo.user"
:options="dataStore.profiles"
option-attribute="fullName"
value-attribute="id"
searchable
:search-attributes="['fullName']"
>
<template #label>
{{dataStore.getProfileById(itemInfo.user) ? dataStore.getProfileById(itemInfo.user).fullName : "Mitarbeiter auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup label="Start:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.start ? dayjs(itemInfo.start).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.start" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup label="Ende:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
variant="outline"
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.end ? dayjs(itemInfo.end).format('DD.MM.YYYY') : 'Datum auswählen'"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.end" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="itemInfo.note"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
</style>

View File

@@ -1,140 +0,0 @@
<template>
<UDashboardNavbar title="Abwesenheiten" :badge="filteredRows ? filteredRows.length : null">
<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(`/absenceRequests/create`)">+ Abwesenheit</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(`/absenceRequests/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Abwesenheiten anzuzeigen' }"
>
<template #approved-data="{row}">
<span v-if="!row.approved">
Genehmigung offen
</span>
<span
v-else-if="row.approved"
class="text-primary"
>
Genemigt
</span>
<span
v-else
class="text-rose"
>
Abgelehnt
</span>
</template>
<template #user-data="{row}">
{{dataStore.profiles.find(profile => profile.id === row.user) ? dataStore.profiles.find(profile => profile.id === row.user).fullName : ""}}
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/absenceRequests/create")
}
})
const dataStore = useDataStore()
const router = useRouter()
const templateColumns = [
{
key: "approved",
label: "Genehmigt",
sortable: true
},
{
key: "user",
label: "Mitarbeiter",
sortable: true
},{
key: "reason",
label: "Grund",
sortable: true
},{
key: "start",
label: "Start",
sortable: true
},{
key: "end",
label: "Ende",
sortable: true
},{
key: "note",
label: "Notizen",
sortable: true
}
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
const searchString = ref('')
const filteredRows = computed(() => {
let items = dataStore.absencerequests
if(!searchString.value) {
return items
}
return items.filter(item => {
return Object.values(item).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
})
</script>
<style scoped>
</style>

View File

@@ -20,6 +20,7 @@ defineShortcuts({
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const router = useRouter()
const supabase = useSupabaseClient()
@@ -27,7 +28,7 @@ const bankstatements = ref([])
const setupPage = async () => {
bankstatements.value = (await supabase.from("bankstatements").select("*, statementallocations(*)").eq('tenant', dataStore.currentTenant).order("date", {ascending:false})).data
bankstatements.value = (await supabase.from("bankstatements").select("*, statementallocations(*)").eq('tenant', profileStore.currentTenant).order("date", {ascending:false})).data
}

View File

@@ -14,6 +14,7 @@ defineShortcuts({
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const mode = ref(route.params.mode || "show")
@@ -108,10 +109,12 @@ const calculateAllocatedSum = computed(() => {
let startingAmount = 0
itemInfo.value.statementallocations.forEach(item => {
console.log(item)
if(item.cd_id) {
startingAmount = startingAmount + item.amount
} else if(item.ii_id) {
startingAmount = Number(startingAmount) - item.amount
startingAmount = Number(startingAmount) + item.amount
}
})
@@ -132,7 +135,7 @@ const saveAllocations = async () => {
const saveAllocation = async (allocation) => {
const {data,error} = await supabase.from("statementallocations").insert({
...allocation,
tenant: dataStore.currentTenant
tenant: profileStore.currentTenant
}).select()
if(data) {
@@ -251,92 +254,95 @@ setup()
</template>
<table class="w-full" v-if="itemInfo.id">
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Buchungsdatum:</span>
</td>
<td>
{{dayjs(itemInfo.date).format("DD.MM.YYYY")}}
</td>
</tr>
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Wertstellungsdatum:</span>
</td>
<td>
{{dayjs(itemInfo.valueDate).format("DD.MM.YYYY")}}
</td>
</tr>
<tr class="flex-row flex justify-between text-right">
<td>
<span class="font-semibold">Partner:</span>
</td>
<td>
{{itemInfo.amount > 0 ? itemInfo.debName : itemInfo.credName}}
<tbody>
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Buchungsdatum:</span>
</td>
<td>
{{dayjs(itemInfo.date).format("DD.MM.YYYY")}}
</td>
</tr>
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Wertstellungsdatum:</span>
</td>
<td>
{{dayjs(itemInfo.valueDate).format("DD.MM.YYYY")}}
</td>
</tr>
<tr class="flex-row flex justify-between text-right">
<td>
<span class="font-semibold">Partner:</span>
</td>
<td>
{{itemInfo.amount > 0 ? itemInfo.debName : itemInfo.credName}}
</td>
</tr>
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Partner IBAN:</span>
</td>
<td>
{{itemInfo.amount > 0 ? separateIBAN(itemInfo.debIban) : separateIBAN(itemInfo.credIban)}}
</td>
</tr>
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Konto:</span>
</td>
<td>
{{dataStore.getBankAccountById(itemInfo.account) ? dataStore.getBankAccountById(itemInfo.account).name || separateIBAN(dataStore.getBankAccountById(itemInfo.account).iban) : ""}}
</td>
</tr>
<tr class="flex-row flex justify-between">
<td colspan="2">
<span class="font-semibold">Verknüpfte Dokumente:</span>
</td>
</tr>
<tr
class="flex-row flex justify-between mb-3"
v-for="item in itemInfo.statementallocations"
>
</td>
</tr>
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Partner IBAN:</span>
</td>
<td>
{{itemInfo.amount > 0 ? separateIBAN(itemInfo.debIban) : separateIBAN(itemInfo.credIban)}}
</td>
</tr>
<tr class="flex-row flex justify-between">
<td>
<span class="font-semibold">Konto:</span>
</td>
<td>
{{dataStore.getBankAccountById(itemInfo.account) ? dataStore.getBankAccountById(itemInfo.account).name || separateIBAN(dataStore.getBankAccountById(itemInfo.account).iban) : ""}}
</td>
</tr>
<tr class="flex-row flex justify-between">
<td colspan="2">
<span class="font-semibold">Verknüpfte Dokumente:</span>
</td>
</tr>
<tr
class="flex-row flex justify-between mb-3"
v-for="item in itemInfo.statementallocations"
>
<td>
<!-- <span v-if="itemInfo.createdDocument"><nuxt-link :to="`/createDocument/show/${itemInfo.createdDocument}`">{{dataStore.createddocuments.find(i => i.id === itemInfo.createdDocument).documentNumber}}</nuxt-link></span>
<span v-else-if="itemInfo.incomingInvoice"><nuxt-link :to="`/incominInvoices/show/${itemInfo.incomingInvoice}`">{{dataStore.getIncomingInvoiceById(itemInfo.incomingInvoice).reference}}</nuxt-link></span>-->
<span v-if="item.cd_id">
{{dataStore.getCreatedDocumentById(item.cd_id).documentNumber}}
</span>
<span v-else-if="item.ii_id">
{{dataStore.getVendorById(dataStore.getIncomingInvoiceById(item.ii_id).vendor).name}} - {{dataStore.getIncomingInvoiceById(item.ii_id).reference}}
</span>
</td>
<td>
<UButton
variant="outline"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="router.push(`/createDocument/show/${item.cd_id}`)"
v-if="item.cd_id"
/>
<UButton
variant="outline"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="router.push(`/incominginvoices/show/${item.ii_id}`)"
v-else-if="item.ii_id"
/>
</td>
</tr>
<tr class="flex-row flex justify-between">
<td colspan="2">
<span class="font-semibold">Beschreibung:</span>
</td>
</tr>
<tr>
<td colspan="2">
{{itemInfo.text}}
</td>
</tr>
</tbody>
<td>
<!-- <span v-if="itemInfo.createdDocument"><nuxt-link :to="`/createDocument/show/${itemInfo.createdDocument}`">{{dataStore.createddocuments.find(i => i.id === itemInfo.createdDocument).documentNumber}}</nuxt-link></span>
<span v-else-if="itemInfo.incomingInvoice"><nuxt-link :to="`/incominInvoices/show/${itemInfo.incomingInvoice}`">{{dataStore.getIncomingInvoiceById(itemInfo.incomingInvoice).reference}}</nuxt-link></span>-->
<span v-if="item.cd_id">
{{dataStore.getCreatedDocumentById(item.cd_id).documentNumber}}
</span>
<span v-else-if="item.ii_id">
{{dataStore.getVendorById(dataStore.getIncomingInvoiceById(item.ii_id).vendor).name}} - {{dataStore.getIncomingInvoiceById(item.ii_id).reference}}
</span>
</td>
<td>
<UButton
variant="outline"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="router.push(`/createDocument/show/${item.cd_id}`)"
v-if="item.cd_id"
/>
<UButton
variant="outline"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="router.push(`/incominginvoices/show/${item.ii_id}`)"
v-else-if="item.ii_id"
/>
</td>
</tr>
<tr class="flex-row flex justify-between">
<td colspan="2">
<span class="font-semibold">Beschreibung:</span>
</td>
</tr>
<tr>
<td colspan="2">
{{itemInfo.text}}
</td>
</tr>
</table>
</UCard>

View File

@@ -6,6 +6,7 @@ import timeGridPlugin from "@fullcalendar/timegrid"
import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
import interactionPlugin from "@fullcalendar/interaction";
import dayjs from "dayjs";
import profiles from "~/components/columnRenderings/profiles.vue";
definePageMeta({
middleware: "auth"
@@ -17,9 +18,10 @@ const router = useRouter()
const mode = ref(route.params.mode || "grid")
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const resources = dataStore.getResources
const eventTypes = dataStore.getEventTypes
//const resources = dataStore.getResources
//const eventTypes = dataStore.getEventTypes
const profileStore = useProfileStore()
//Working
const newEventData = ref({
@@ -36,66 +38,40 @@ const showEventModal = ref(false)
const selectedEvent = ref({})
const selectedResources = ref([])
const events = ref([])
const calendarOptionsGrid = computed(() => {
return {
locale: deLocale,
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
initialView: "timeGridWeek",
initialEvents: events.value,
nowIndicator: true,
height: "80vh",
selectable: true,
weekNumbers: true,
select: function (info) {
console.log(info)
//Functions
const convertResourceIds = () => {
newEventData.value.resources = selectedResources.value.map(i => {
/*if(i.type !== 'Mitarbeiter') {
return {id: Number(i.id.split('-')[1]), type: i.type}
} else {
return {id: i.id, type: i.type}
}*/
if((String(i).match(/-/g) || []).length > 1) {
return {type: "Mitarbeiter", id: i}
} else {
if(i.split('-')[0] === 'I') {
return {id: Number(i.split('-')[1]), type: "Inventar"}
} else if(i.split('-')[0] === 'F') {
return {id: Number(i.split('-')[1]), type: "Fahrzeug"}
router.push(`/standardEntity/events/create/?startDate=${encodeURIComponent(info.startStr)}&endDate=${encodeURIComponent(info.endStr)}`)
},
eventClick: function (info) {
console.log(info.event)
if(info.event.extendedProps.entrytype === "absencerequest"){
router.push(`/standardEntity/absencerequests/show/${info.event.extendedProps.absencerequestId}`)
} else {
router.push(`/standardEntity/events/show/${info.event.extendedProps.eventId}`)
}
}
})
}
//Calendar Config
const calendarOptionsGrid = reactive({
locale: deLocale,
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
initialView: "dayGridMonth",
initialEvents: dataStore.getEvents,
nowIndicator: true,
height: "80vh",
selectable: true,
weekNumbers: true,
select: function(info) {
console.log(info)
router.push(`/events/edit/?start=${info.startStr}&end=${info.endStr}&source=grid`)
},
eventClick: function (info){
console.log(info)
if(info.event.title.startsWith("Abw.:")){
router.push(`/absencerequests/show/${info.event.id}`)
} else {
router.push(`/events/show/${info.event.id}`)
}
},
},
}
})
const calendarOptionsTimeline = reactive({
const calendarOptionsTimeline = ref({
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
locale: deLocale,
plugins: [resourceTimelinePlugin, interactionPlugin],
@@ -106,21 +82,26 @@ const calendarOptionsTimeline = reactive({
center: 'title',
right: 'resourceTimelineDay,resourceTimeline3Hours,resourceTimelineMonth'
},
initialEvents: dataStore.getEventsByResource,
initialEvents: [{
title:"Test",
resourceId:"F-27",
start:"2025-01-01"
}],
selectable: true,
select: function (info) {
router.push(`/events/edit/?start=${info.startStr}&end=${info.endStr}&resources=${JSON.stringify([info.resource.id])}&source=timeline`)
router.push(`/events/edit/?startDate=${info.startStr}&endDate=${info.endStr}&resources=${JSON.stringify([info.resource.id])}&source=timeline`)
},
eventClick: function (info){
if(info.event.title.startsWith("Abw.:")){
router.push(`/absencerequests/show/${info.event.id}`)
console.log(info.event)
if(info.event.extendedProps.entrytype === "absencerequest"){
router.push(`/standardEntity/absencerequests/show/${info.event.extendedProps.absencerequestId}`)
} else {
router.push(`/events/show/${info.event.id}`)
router.push(`/standardEntity/events/show/${info.event.extendedProps.eventId}`)
}
},
resourceGroupField: "type",
resourceOrder: "-type",
resources: resources,
resources: [],
nowIndicator:true,
views: {
resourceTimeline3Hours: {
@@ -146,25 +127,280 @@ const calendarOptionsTimeline = reactive({
height: '80vh',
})
const loaded = ref(false)
const setupPage = async () => {
let tempData = await useSupabaseSelect("events", "*, vehicles(*), inventoryitems(*)")
let absencerequests = await useSupabaseSelect("absencerequests", "*")
let projects = await useSupabaseSelect("projects", "*")
let inventoryitems = await useSupabaseSelect("inventoryitems", "*")
let profiles = await useSupabaseSelect("profiles", "*")
let vehicles = await useSupabaseSelect("vehicles", "*")
/*events.value = [
...tempData.map(event => {
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
let title = ""
if (event.name) {
title = event.name
} else if (event.project) {
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
}
return {
...event,
start: event.startDate,
end: event.endDate,
title: title,
borderColor: eventColor,
textColor: eventColor,
backgroundColor: "black"
}
}),
...absencerequests.map(absence => {
return {
id: absence.id,
resourceId: absence.user,
resourceType: "person",
title: `Abw.: ${absence.reason}`,
start: dayjs(absence.start).toDate(),
end: dayjs(absence.end).add(1, 'day').toDate(),
allDay: true,
}
})
]*/
calendarOptionsGrid.value.initialEvents = [
...tempData.map(event => {
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
let title = ""
if (event.name) {
title = event.name
} else if (event.project) {
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
}
return {
...event,
start: event.startDate,
end: event.endDate,
title: title,
borderColor: eventColor,
textColor: eventColor,
backgroundColor: "black",
entrytype: "event",
eventId: event.id
}
}),
...absencerequests.map(absence => {
return {
id: absence.id,
resourceId: absence.user,
resourceType: "person",
title: `${absence.reason} - ${absence.name}`,
start: dayjs(absence.start).toDate(),
end: dayjs(absence.end).add(1, 'day').toDate(),
allDay: true,
absencerequestId: absence.id,
entrytype: "absencerequest",
}
})
]
calendarOptionsTimeline.value.resources = [
...profiles.filter(i => i.tenant === profileStore.currentTenant).map(profile => {
return {
type: 'Mitarbeiter',
title: profile.fullName,
id: profile.id
}
}),
...vehicles.map(vehicle => {
return {
type: 'Fahrzeug',
title: vehicle.licensePlate,
id: `F-${vehicle.id}`
}
}),
...inventoryitems.filter(i=> i.usePlanning).map(item => {
return {
type: 'Inventar',
title: item.name,
id: `I-${item.id}`
}
})
]
console.log(calendarOptionsTimeline.value.resources)
/*
calendarOptionsTimeline.value.initialEvents = [
...events.value.map(event => {
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
let title = ""
if (event.name) {
title = event.name
} else if (event.project) {
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
}
let resource = ""
let resourceType = ""
return {
...event,
start: event.startDate,
end: event.endDate,
title: title,
borderColor: eventColor,
textColor: eventColor,
backgroundColor: "black"
}
})
]
*/
let tempEvents = []
tempData.forEach(event => {
console.log(event)
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
let title = ""
if (event.name) {
title = event.name
} else if (event.project) {
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
}
let returnData = {
title: title,
borderColor: eventColor,
textColor: eventColor,
backgroundColor: "black",
start: event.startDate,
end: event.endDate,
resourceIds: [],
entrytype: "event",
eventId: event.id
}
if(event.profiles.length > 0) {
console.log("Profiles found")
event.profiles.forEach(profile => {
returnData.resourceIds.push(profile)
})
}
if(event.vehicles.length > 0) {
console.log("Vehicles found")
event.vehicles.forEach(vehicle => {
returnData.resourceIds.push(`F-${vehicle.id}`)
})
}
if(event.inventoryitems.length > 0) {
console.log("Inventoryitems found")
event.inventoryitems.forEach(inventoryitem => {
returnData.resourceIds.push(`I-${inventoryitem.id}`)
})
}
tempEvents.push(returnData)
})
absencerequests.forEach(absencerequest => {
let returnData = {
title: `${absencerequest.reason} - ${absencerequest.name}`,
backgroundColor: "black",
start: absencerequest.startDate,
end: absencerequest.endDate,
resourceIds: [absencerequest.profile],
entrytype: "absencerequest",
allDay: true,
absencerequestId: absencerequest.id
}
tempEvents.push(returnData)
})
calendarOptionsTimeline.value.initialEvents = tempEvents
console.log(calendarOptionsTimeline.value)
loaded.value = true
}
setupPage()
//Functions
/*
const convertResourceIds = () => {
newEventData.value.resources = selectedResources.value.map(i => {
/!*if(i.type !== 'Mitarbeiter') {
return {id: Number(i.id.split('-')[1]), type: i.type}
} else {
return {id: i.id, type: i.type}
}*!/
if((String(i).match(/-/g) || []).length > 1) {
return {type: "Mitarbeiter", id: i}
} else {
if(i.split('-')[0] === 'I') {
return {id: Number(i.split('-')[1]), type: "Inventar"}
} else if(i.split('-')[0] === 'F') {
return {id: Number(i.split('-')[1]), type: "Fahrzeug"}
}
}
})
}
*/
//Calendar Config
</script>
<template>
<UDashboardNavbar :title="currentItem ? currentItem.name : ''">
<template #right>
<UDashboardNavbar title="Kalender">
<template #center>
<h1
:class="['text-xl','font-medium']"
>Kalender</h1>
</template>
<template #right>
</template>
</UDashboardNavbar>
<div v-if="mode === 'grid'" class="p-5">
<FullCalendar
:options="calendarOptionsGrid"
/>
</div>
<div v-else-if="mode === 'timeline'" class="p-5">
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<div v-if="mode === 'grid' && loaded" class="p-5">
<FullCalendar
:options="calendarOptionsGrid"
/>
</div>
<div v-else-if="mode === 'timeline' && loaded" class="p-5">
<FullCalendar
:options="calendarOptionsTimeline"
/>
</div>
</UDashboardPanelContent>
</template>
<style scoped>

View File

@@ -11,6 +11,7 @@ const supabase = useSupabaseClient()
const user = useSupabaseUser()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const selectedChat = ref({})
const messageText = ref("")
@@ -24,8 +25,8 @@ const messageText = ref("")
@click="selectedChat = chat"
>
<UAlert
:title="chat.title ? chat.title : chat.members.map(i => { if(i !== user.id) return dataStore.getProfileById(i).fullName}).join(' ')"
:avatar="{alt: chat.members.map(i => { if(i !== user.id) return dataStore.getProfileById(i).fullName}).join(' ')}"
:title="chat.title ? chat.title : chat.members.map(i => { if(i !== user.id) return profileStore.getProfileById(i).fullName}).join(' ')"
:avatar="{alt: chat.members.map(i => { if(i !== user.id) return profileStore.getProfileById(i).fullName}).join(' ')}"
:color="selectedChat.id === chat.id ? 'primary' : 'white'"
variant="outline"
>
@@ -49,13 +50,13 @@ const messageText = ref("")
{{message.text}}
</div>
<UAvatar
:alt="dataStore.getProfileById(message.origin) ? dataStore.getProfileById(message.origin).fullName : ''"
:alt="profileStore.getProfileById(message.origin) ? profileStore.getProfileById(message.origin).fullName : ''"
size="md"
/>
</div>
<div class="flex justify-start mb-4" v-else>
<UAvatar
:alt="dataStore.getProfileById(message.origin) ? dataStore.getProfileById(message.origin).fullName : ''"
:alt="profileStore.getProfileById(message.origin) ? profileStore.getProfileById(message.origin).fullName : ''"
size="md"
/>
<div

View File

@@ -1,6 +1,6 @@
<script setup>
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const profiles = ref([])
@@ -10,14 +10,14 @@ const selectedProfiles = ref([])
const setup = async () => {
profiles.value = await useSupabaseSelect("profiles")
selectedProfiles.value = [dataStore.activeProfile.id]
selectedProfiles.value = [profileStore.activeProfile.id]
}
setup()
const createChat = async () => {
const {data,error} = await supabase.from("chats").insert({
tenant: dataStore.currentTenant,
tenant: profileStore.currentTenant,
name: itemInfo.value.name
}).select()
@@ -105,7 +105,7 @@ const createChat = async () => {
:search-attributes="['fullName']"
>
<template #label>
{{selectedProfiles.length > 0 ? selectedProfiles.map(i => dataStore.getProfileById(i).fullName).join(", ") : "Keine Benutzer ausgewählt"}}
{{selectedProfiles.length > 0 ? selectedProfiles.map(i => profileStore.getProfileById(i).fullName).join(", ") : "Keine Benutzer ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>

View File

@@ -13,17 +13,17 @@ defineShortcuts({
})
const itemInfo = ref({})
const dataStore = useDataStore()
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(dataStore.activeProfile.id))
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, dataStore.activeProfile.id]}).eq("id",message.id)
await supabase.from("chatmessages").update({seenBy: [...message.seenBy, profileStore.activeProfile.id]}).eq("id",message.id)
}
@@ -34,11 +34,11 @@ const messageText = ref("")
const sendMessage = async () => {
if(messageText.value.length > 0) {
const message = {
origin: dataStore.activeProfile.id,
origin: profileStore.activeProfile.id,
destinationchat: itemInfo.value.id,
text: messageText.value,
tenant: dataStore.currentTenant,
seenBy: [dataStore.activeProfile.id]
tenant: profileStore.currentTenant,
seenBy: [profileStore.activeProfile.id]
}
const {data,error} = await supabase.from("chatmessages").insert(message)
@@ -49,11 +49,11 @@ const sendMessage = async () => {
//Reset
messageText.value = ""
//Create Notifications
let notifications = itemInfo.value.profiles.filter(i => i.id !== dataStore.activeProfile.id).map(i => {
let notifications = itemInfo.value.profiles.filter(i => i.id !== profileStore.activeProfile.id).map(i => {
return {
tenant: dataStore.currentTenant,
tenant: profileStore.currentTenant,
profile: i.id,
initiatingProfile: dataStore.activeProfile.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
@@ -88,13 +88,13 @@ const sendMessage = async () => {
</UDashboardNavbar>
<div class="scrollList p-5">
<UAlert
:color="message.seenBy.includes(dataStore.activeProfile.id) ? 'white' : 'primary'"
:variant="message.seenBy.includes(dataStore.activeProfile.id) ? 'solid' : 'outline'"
: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: dataStore.getProfileById(message.origin).fullName }"
:title="`${dataStore.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')}`"
: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>

View File

@@ -1,337 +0,0 @@
<script setup>
import HistoryDisplay from "~/components/HistoryDisplay.vue";
import dayjs from "dayjs";
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/checks")
},
'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)
const vehicles = ref([])
const profiles = ref([])
const inventoryitems = ref([])
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
name: "",
vehicle: null,
inventoryitem: null,
profile: null,
distance: 1,
distanceUnit: "days"
})
//Functions
const setupPage = async () => {
profiles.value = await useSupabaseSelect("profiles")
vehicles.value = await useSupabaseSelect("vehicles")
inventoryitems.value = await useSupabaseSelect("inventoryitems")
if(mode.value === "show" ){
itemInfo.value = await useSupabaseSelectSingle("checks", route.params.id, "*, checkexecutions(*, executed_by(fullName)), vehicle(*), profile(*), inventoryitem(*)")
} else if (mode.value === "edit"){
itemInfo.value = await useSupabaseSelectSingle("checks", route.params.id, "*")
}
if(mode.value === "create") {
let query = route.query
}
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/checks/show/${itemInfo.value.id}`)
} else {
router.push(`/checks/`)
}
}
setupPage()
</script>
<template>
<UDashboardNavbar>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/checks`)"
>
Überprüfungen
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium']"
>{{itemInfo ? `Überprüfung: ${itemInfo.name}` : (mode === 'create' ? 'Überprüfung erstellen' : 'Überprüfung bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('checks',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('checks',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/checks/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UTabs
v-if="itemInfo.id && mode === 'show'"
:items="[{label: 'Informationen'}, {label: 'Dokumente'}, {label: 'Ausführungen'}]"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="flex mt-5">
<div class="w-1/2 mr-5">
<UCard>
<div class="text-wrap">
<table class="mb-3 w-full">
<tr>
<td>Name:</td>
<td>{{itemInfo.name}}</td>
</tr>
<tr>
<td>Rhythmus:</td>
<td>
{{itemInfo.distance}}
<span v-if="itemInfo.distanceUnit === 'dayjs'">Tage</span>
<span v-if="itemInfo.distanceUnit === 'years'">Jahre</span>
</td>
</tr>
<tr v-if="itemInfo.vehicle">
<td>Fahrzeug:</td>
<td>{{itemInfo.vehicle.licensePlate}}</td>
</tr>
<tr v-if="itemInfo.profile">
<td>Person:</td>
<td>{{itemInfo.profile.fullName}}</td>
</tr>
<tr v-if="itemInfo.inventoryitem">
<td>Inventarartikel:</td>
<td>{{itemInfo.inventoryitem.name}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.description}}</td>
</tr>
</table>
</div>
</UCard>
</div>
<div class="w-1/2">
<UCard class="h-full">
<HistoryDisplay
type="check"
v-if="itemInfo"
:element-id="itemInfo.id"
:render-headline="true"
/>
</UCard>
</div>
</div>
<div v-else-if="item.label === 'Dokumente'">
<UCard class="mt-5">
<Toolbar>
<DocumentUpload
type="check"
:element-id="itemInfo.id"
/>
</Toolbar>
<DocumentList
:documents="dataStore.getDocumentsByContractId(itemInfo.id)"
/>
</UCard>
</div>
<div v-else-if="item.label === 'Ausführungen'">
<UCard class="mt-5">
<UTable
:rows="itemInfo.checkexecutions"
:columns="[{key:'created_at',label:'Ersellt am:'},{key:'executed_at',label:'Ausgeführt am:'},{key:'executed_by',label:'Ausgeführt von:'},{key:'description',label:'Beschreibung:'}]"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Ausführungen anzuzeigen' }"
>
<template #created_at-data="{row}">
{{dayjs(row.created_at).format("DD.MM.YYYY HH:mm")}}
</template>
<template #executed_at-data="{row}">
{{dayjs(row.executed_at).format("DD.MM.YYYY HH:mm")}}
</template>
<template #executed_by-data="{row}">
{{row.executed_by.fullName}}
</template>
</UTable>
</UCard>
</div>
</template>
</UTabs>
<UForm v-else-if="mode === 'edit' || mode === 'create'" class="p-5" >
<div class="mx-auto w-4/5">
<div class="flex flex-row justify-around">
<div class="w-1/2">
<UDivider>Allgemeines</UDivider>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<UInput
v-model="itemInfo.type"
/>
</UFormGroup>
<UFormGroup
label="Rhythmus"
class="w-full"
>
<InputGroup class="w-full">
<UInput
v-model="itemInfo.distance"
type="number"
class="flex-auto"
></UInput>
<USelectMenu
:options="[{key: 'days', label: 'Tage'},{key:'years',label:'Jahre'}]"
option-attribute="label"
value-attribute="key"
v-model="itemInfo.distanceUnit"
class="w-40"
></USelectMenu>
</InputGroup>
</UFormGroup>
</div>
<div class=" ml-5 w-1/2">
<UDivider>Verknüpftes Element</UDivider>
<UFormGroup
label="Fahrzeug:"
>
<USelectMenu
:options="vehicles"
option-attribute="licensePlate"
value-attribute="id"
v-model="itemInfo.vehicle"
:disabled="itemInfo.inventoryitem || itemInfo.profile"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Person:"
>
<USelectMenu
:options="profiles"
option-attribute="fullName"
value-attribute="id"
v-model="itemInfo.profile"
:disabled="itemInfo.vehicle || itemInfo.inventoryitem"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Inventarartikel:"
>
<USelectMenu
:options="inventoryitems"
option-attribute="name"
value-attribute="id"
v-model="itemInfo.inventoryitem"
:disabled="itemInfo.vehicle || itemInfo.profile"
>
</USelectMenu>
</UFormGroup>
</div>
</div>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
rows="6"
maxrows="12"
/>
</UFormGroup>
</div>
</UForm>
</UDashboardPanelContent>
</template>
<style scoped>
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,148 +0,0 @@
<template>
<UDashboardNavbar title="Überprüfungen" :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(`/checks/create`)">+ Überprüfung</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(`/checks/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Überprüfungen 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 #vehicle-data="{row}">
<span v-if="row.vehicle">{{row.vehicle.licensePlate}}</span>
</template>
<template #profile-data="{row}">
<span v-if="row.profile">{{row.profile.fullName}}</span>
</template>
<template #inventoryitem-data="{row}">
<span v-if="row.inventoryitem">{{row.inventoryitem.name}}</span>
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/checks/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/checks/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("checks","*, vehicle(licensePlate), profile(fullName), inventoryitem(name)")
}
setupPage()
const templateColumns = [
{
key: "name",
label: "Name",
sortable: true
},
{
key: "vehicle",
label: "Fahrzeug"
},
{
key: "profile",
label: "Person"
},
{
key: "inventoryitem",
label: "Inventarartikel"
},
{
key: "notes",
label: "Notizen"
}
]
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>

View File

@@ -3,6 +3,7 @@ import dayjs from "dayjs"
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const router = useRouter()
@@ -10,7 +11,7 @@ const items = ref([])
const setup = async () => {
items.value = (await supabase.from("historyitems").select().like('text',`%@${dataStore.activeProfile.username}%`)/*.textSearch("text", `'@${dataStore.activeProfile.username}'`)*/.order("created_at")).data
items.value = (await supabase.from("historyitems").select().like('text',`%@${profileStore.activeProfile.username}%`)/*.textSearch("text", `'@${profileStore.activeProfile.username}'`)*/.order("created_at")).data
}
const navigateToHistoryItem = (item) => {

View File

@@ -23,6 +23,7 @@ defineShortcuts({
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
@@ -33,7 +34,8 @@ const openTab = ref(0)
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
active: true
active: true,
profiles: [profileStore.activeProfile.id]
})
const oldItemInfo = ref({})
@@ -54,21 +56,11 @@ const setupPage = async () => {
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/contacts/show/${itemInfo.value.id}`)
} else {
router.push(`/contacts`)
}
}
setupPage()
</script>
<template>
<UDashboardNavbar
:title="itemInfo ? itemInfo.fullName : (mode === 'create' ? 'Ansprechpartner erstellen' : 'Ansprechpartner bearbeiten')"
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
>
<template #left>
@@ -84,7 +76,7 @@ setupPage()
<h1
v-if="itemInfo"
:class="['text-xl','font-medium', ... itemInfo.active ? ['text-primary'] : ['text-rose-500']]"
>{{itemInfo ? `Ansprechpartner: ${itemInfo.fullName}` : (mode === 'create' ? 'Ansprechpartner erstellen' : 'Ansprechpartner bearbeiten')}}</h1>
>{{itemInfo.id ? `Ansprechpartner: ${itemInfo.fullName}` : (mode === 'create' ? 'Ansprechpartner erstellen' : 'Ansprechpartner bearbeiten')}}</h1>
</template>
<template #right>
<UButton
@@ -100,7 +92,7 @@ setupPage()
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
@click="router.push(itemInfo.id ? `/contacts/show/${itemInfo.value.id}` : `/contacts/`)"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
@@ -310,6 +302,23 @@ setupPage()
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Berechtige Benutzer:"
>
<USelectMenu
v-model="itemInfo.profiles"
:options="profileStore.profiles"
option-attribute="fullName"
value-attribute="id"
searchable
multiple
:search-attributes="['fullName']"
>
<template #label>
{{itemInfo.profiles.length > 0 ? itemInfo.profiles.map(i => profileStore.getProfileById(i).fullName).join(", ") : "Kein Benutzer ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
@@ -317,6 +326,7 @@ setupPage()
v-model="itemInfo.notes"
/>
</UFormGroup>
</UForm>
</template>

View File

@@ -1,182 +1,24 @@
<template>
<UDashboardNavbar title="Ansprechpartner" :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(`/contacts/create`)">+ Kontakt</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(`/contacts/show/${i.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Ansprechpartner anzuzeigen' }"
>
<template #fullName-data="{row}">
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.fullName}}</span>
<span v-else>{{row.fullName}}</span>
</template>
<template #active-data="{row}">
<span v-if="row.active" class="text-primary-500">Aktiv</span>
<span v-else class="text-rose">Gesperrt</span>
</template>
<template #customer-data="{row}">
{{row.customer ? row.customer.name : ""}}
</template>
<template #vendor-data="{row}">
{{row.vendor ? row.vendor.name : ""}}
</template>
</UTable>
<EntityList
type="contacts"
:items="items"
></EntityList>
</template>
<script setup>
import {useSearch} from "~/composables/useSearch.js";
import EntityList from "~/components/EntityList.vue";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/contacts/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/contacts/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("contacts","*, customer(name), vendor(name)")
}
setupPage()
const templateColumns = [
{
key: "fullName",
label: "Name",
sortable: true
},
{
key: "customer",
label: "Kunde",
sortable: true
},
{
key: "vendor",
label: "Lieferant",
sortable: true
},
{
key: "role",
label: "Rolle",
},
{
key: "email",
label: "E-Mail",
},
{
key: "phoneMobile",
label: "Mobil",
},
{
key: "phoneHome",
label: "Festnetz",
},
{
key: "active",
label: "Aktiv",
},
{
key: "birtday",
label: "Geburtstag",
}
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
const searchString = ref('')
/*const filteredRows = computed(() => {
if(!searchString.value) {
return dataStore.contacts
}
return dataStore.contacts.filter(item => {
return Object.values(item).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
})*/
const filteredRows = computed(() => {
return useSearch(searchString.value, items.value)
})
</script>
<style scoped>

View File

@@ -1,534 +0,0 @@
<script setup>
import HistoryDisplay from "~/components/HistoryDisplay.vue";
import dayjs from "dayjs";
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/contracts")
},
'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: "",
customer: null,
active: true,
ownFields: {}
})
//Functions
const setupPage = async () => {
if(mode.value === "show" ){
itemInfo.value = await useSupabaseSelectSingle("contracts", route.params.id, "*, customer(id,name,isCompany, customerNumber), contact(id,fullName)")
} else if (mode.value === "edit"){
itemInfo.value = await useSupabaseSelectSingle("contracts", route.params.id, "*")
}
if(mode.value === "create") {
let query = route.query
if(query.customer) itemInfo.value.customer = Number(query.customer)
}
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/contracts/show/${itemInfo.value.id}`)
} else {
router.push(`/contracts/`)
}
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Vertrag erstellen' : 'Vertrag bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/contracts`)"
>
Verträge
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium', ... itemInfo.active ? ['text-primary'] : ['text-rose-500']]"
>{{itemInfo ? `Vertrag: ${itemInfo.name}` : (mode === 'create' ? 'Vertrag erstellen' : 'Vertrag bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('contracts',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('contracts',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/contracts/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UTabs
v-if="itemInfo.id && mode === 'show'"
:items="[{label: 'Informationen'}, {label: 'Dokumente'}]"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="flex mt-5">
<div class="w-1/2 mr-5">
<UCard>
<div class="text-wrap">
<table class="mb-3">
<tr>
<td>Name:</td>
<td>{{itemInfo.name}}</td>
</tr>
<tr>
<td>Aktiv:</td>
<td>
<span v-if="itemInfo.active" class="text-primary-500">Ja</span>
<span v-else class="text-rose-600">Nein</span>
</td>
</tr>
<tr>
<td>Kunde:</td>
<td>
<nuxt-link
:to="`/customers/show/${itemInfo.customer.id}`"
v-if="itemInfo.customer"
>{{itemInfo.customer.customerNumber ? `${itemInfo.customer.customerNumber} - ` : ''}}{{itemInfo.customer ? itemInfo.customer.name : ""}}</nuxt-link>
</td>
</tr>
<tr v-if="itemInfo.customer.isCompany">
<td>Ansprechpartner:</td>
<td>
<nuxt-link
v-if="itemInfo.contact"
:to="`/contacts/show/${itemInfo.contact.id}`"
>{{itemInfo.contact ? itemInfo.contact.fullName : ""}}</nuxt-link>
</td>
</tr>
<tr>
<td>Wiederkehrend:</td>
<td>{{itemInfo.recurring ? "Ja" : "Nein"}}</td>
</tr>
<tr>
<td>Startdatum:</td>
<td>{{dayjs(itemInfo.startDate).format("DD.MM.YYYY")}}</td>
</tr>
<tr v-if="!itemInfo.active">
<td>Enddatum:</td>
<td>{{dayjs(itemInfo.endDate).format("DD.MM.YYYY")}}</td>
</tr>
<tr>
<td>Unterschrieben am:</td>
<td>{{dayjs(itemInfo.signDate).format("DD.MM.YYYY")}}</td>
</tr>
<tr>
<td>Laufzeit:</td>
<td>{{itemInfo.duration}}</td>
</tr>
<tr>
<td>Rechnungsversand:</td>
<td>{{itemInfo.invoiceDispatch}}</td>
</tr>
<tr>
<td>Zahlart:</td>
<td>{{itemInfo.paymentType}}</td>
</tr>
<tr v-if="itemInfo.paymentType === 'Einzug'">
<td>SEPA Mandatsreferenz:</td>
<td>{{itemInfo.sepaRef}}</td>
</tr>
<tr v-if="itemInfo.paymentType === 'Einzug'">
<td>SEPA Unterschrieben am:</td>
<td>{{dayjs(itemInfo.sepaDate).format("DD.MM.YYYY")}}</td>
</tr>
<tr>
<td>Bank:</td>
<td>{{itemInfo.bankingName}}</td>
</tr>
<tr>
<td>BIC:</td>
<td>{{itemInfo.bankingBIC}}</td>
</tr>
<tr>
<td>IBAN:</td>
<td>{{itemInfo.bankingIban}}</td>
</tr>
<tr>
<td>Kontoinhaber:</td>
<td>{{itemInfo.bankingOwner}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.notes}}</td>
</tr>
<tr>
<td colspan="2"><span class="font-bold">Eigene Felder:</span></td>
</tr>
<tr v-for="fieldKey in Object.keys(itemInfo.ownFields)">
<td>{{dataStore.ownTenant.ownFields.contracts.find(i => i.key === fieldKey).label}}</td>
<td>{{itemInfo.ownFields[fieldKey]}}</td>
</tr>
</table>
</div>
</UCard>
</div>
<div class="w-1/2">
<UCard class="h-full">
<HistoryDisplay
type="contract"
v-if="itemInfo"
:element-id="itemInfo.id"
:render-headline="true"
/>
</UCard>
</div>
<!-- <UBadge
v-if="itemInfo.active"
>
Vertrag aktiv
</UBadge>
<UBadge
v-else
color="red"
>
Vertrag gesperrt
</UBadge>-->
</div>
<div v-else-if="item.label === 'Dokumente'">
<UCard class="mt-5">
<Toolbar>
<DocumentUpload
type="contract"
:element-id="itemInfo.id"
/>
</Toolbar>
<DocumentList
:documents="dataStore.getDocumentsByContractId(itemInfo.id)"
/>
</UCard>
</div>
</template>
</UTabs>
<UForm v-else-if="mode == 'edit' || mode == 'create'" class="p-5" >
<div class="mx-auto w-4/5">
<div class="flex flex-row justify-around">
<div class="w-1/2">
<UDivider>Vertragsdaten</UDivider>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Kunde:"
>
<USelectMenu
v-model="itemInfo.customer"
option-attribute="name"
value-attribute="id"
:options="dataStore.customers"
@change="itemInfo.contact = null"
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kein Kunde ausgewählt" }}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Ansprechpartner:"
v-if="itemInfo.customer"
>
<USelectMenu
v-model="itemInfo.contact"
option-attribute="fullName"
value-attribute="id"
:options="dataStore.getContactsByCustomerId(itemInfo.customer)"
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.getContactById(itemInfo.contact) ? dataStore.getContactById(itemInfo.contact).fullName : "Kein Ansprechpartner ausgewählt" }}
</template>
</USelectMenu>
</UFormGroup>
<InputGroup>
<UFormGroup
label="Vertrag aktiv:"
>
<UCheckbox
v-model="itemInfo.active"
/>
</UFormGroup>
<UFormGroup
label="Vertrag wiederkehrend:"
>
<UCheckbox
v-model="itemInfo.recurring"
/>
</UFormGroup>
</InputGroup>
<InputGroup>
<UFormGroup
label="Vertragsstart:"
class="mt-2"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.startDate ? dayjs(itemInfo.startDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.startDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Vertragsende(voraussichtlich):"
class="mt-2"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.endDate ? dayjs(itemInfo.endDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.endDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
</InputGroup>
<UFormGroup
label="mindest Vertragslaufzeit:"
>
<USelectMenu
:options="['12 Monate','24 Monate','36 Monate','48 Monate']"
v-model="itemInfo.duration"
/>
</UFormGroup>
<UFormGroup
label="Datum der Unterzeichnung:"
class="mt-2"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.signDate ? dayjs(itemInfo.signDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.signDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
</div>
<div class=" ml-5 w-1/2">
<UDivider>Abrechnung</UDivider>
<UFormGroup
label="Rechnungsversand:"
>
<USelectMenu
:options="['E-Mail', 'Post']"
v-model="itemInfo.invoiceDispatch"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Zahlungsart:"
>
<USelectMenu
:options="['Einzug', 'Überweisung']"
v-model="itemInfo.paymentType"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Sepa Mandat"
>
<InputGroup>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.sepaDate ? dayjs(itemInfo.sepaDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.sepaDate" @close="close" />
</template>
</UPopover>
<UInput
placeholder="Mandatsreferenz"
class="flex-auto"
v-model="itemInfo.sepaRef"
/>
</InputGroup>
</UFormGroup>
<UFormGroup
label="Kontodaten:"
>
<InputGroup>
<UInput
v-model="itemInfo.bankingIban"
placeholder="IBAN"
class="w-1/2"
/>
<UInput
v-model="itemInfo.bankingOwner"
class="w-1/2"
placeholder="Inhaber"
/>
</InputGroup>
</UFormGroup>
<UFormGroup
label="Bankdaten:"
>
<InputGroup>
<UInput
v-model="itemInfo.bankingName"
placeholder="Name"
class="w-1/2"
/>
<UInput
v-model="itemInfo.bankingBIC"
placeholder="BIC"
class="w-1/2"
/>
</InputGroup>
</UFormGroup>
</div>
</div>
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="itemInfo.notes"
rows="6"
maxrows="12"
/>
</UFormGroup>
<div
v-if="dataStore.ownTenant.ownFields"
>
<UDivider
class="mt-3"
>Eigene Felder</UDivider>
<UFormGroup
v-for="field in dataStore.ownTenant.ownFields.contracts"
:key="field.key"
:label="field.label"
>
<UInput
v-if="field.type === 'text'"
v-model="itemInfo.ownFields[field.key]"
/>
<USelectMenu
v-else-if="field.type === 'select'"
:options="field.options"
v-model="itemInfo.ownFields[field.key]"
/>
</UFormGroup>
</div>
</div>
</UForm>
</UDashboardPanelContent>
</template>
<style scoped>
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,131 +0,0 @@
<template>
<UDashboardNavbar title="Verträge" :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(`/contracts/create`)">+ Vertrag</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(`/contracts/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Verträge anzuzeigen' }"
>
<template #customer-data="{row}">
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.customer ? row.customer.name : ""}}</span>
<span v-else>{{row.customer ? row.customer.name : ""}}</span>
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/contracts/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/contracts/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("contracts","*, customer(id,name)")
}
setupPage()
const templateColumns = [
{
key: 'customer',
label: "Kunde",
sortable: true
},
{
key: "name",
label: "Name",
sortable: true
},
{
key: "notes",
label: "Notizen"
}
]
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>

File diff suppressed because it is too large Load Diff

View File

@@ -111,7 +111,7 @@
</div>
</template>
<template #amount-data="{row}">
{{displayCurrency(calculateDocSum(row))}}
<span v-if="row.type !== 'deliveryNotes'">{{displayCurrency(calculateDocSum(row))}}</span>
</template>
</UTable>
@@ -162,7 +162,7 @@ const items = ref([])
const selectedItem = ref(0)
const setupPage = async () => {
items.value = await useSupabaseSelect("createddocuments","*, customer(id,name), statementallocations(id,amount)","documentDate")
items.value = (await useSupabaseSelect("createddocuments","*, customer(id,name), statementallocations(id,amount)","documentNumber")).filter(i => !i.archived)
}
setupPage()
@@ -215,6 +215,9 @@ const templateTypes = [
{
key: "invoices",
label: "Rechnungen"
},{
key: "cancellationInvoices",
label: "Stornorechnungen"
},{
key: "advanceInvoices",
label: "Abschlagsrechnungen"
@@ -269,7 +272,7 @@ const calculateDocSum = (row) => {
row.rows.forEach(row => {
if(row.mode === "normal" || row.mode === "service" || row.mode === "free") {
sum += row.quantity * row.price * (1 - row.discountPercent / 100) * (1 + row.taxPercent / 100)
sum += row.quantity * row.price * (1 - row.discountPercent / 100) * (1 + (row.taxPercent || 0) / 100)
}
})
@@ -280,10 +283,6 @@ const isPaid = (item) => {
let amountPaid = 0
item.statementallocations.forEach(allocation => amountPaid += allocation.amount)
console.log(item.documentNumber)
console.log(amountPaid)
console.log(calculateDocSum(item))
return Number(amountPaid.toFixed(2)) === Number(calculateDocSum(item))
}
</script>

View File

@@ -12,6 +12,7 @@ defineShortcuts({
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
@@ -20,13 +21,20 @@ const linkedDocument =ref({})
const currentTenant = ref({})
const setupPage = async () => {
if(route.params) {
if(route.params.id) itemInfo.value = await useSupabaseSelectSingle("createddocuments",route.params.id,"*")
const {data,error} = await supabase.from("documents").select("id").eq("createdDocument", route.params.id).order("id",{ascending:true})
linkedDocument.value = data[data.length -1]
if(route.params.id) itemInfo.value = await useSupabaseSelectSingle("createddocuments",route.params.id,"*, files(*)")
console.log(itemInfo.value)
linkedDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
//const {data,error} = await supabase.from("files").select("id").eq("createddocument", route.params.id).order("id",{ascending:true})
//linkedDocument.value = data[data.length -1]
}
currentTenant.value = (await supabase.from("tenants").select().eq("id",dataStore.currentTenant).single()).data
currentTenant.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
console.log(currentTenant.value)
}
@@ -35,9 +43,9 @@ setupPage()
const openEmail = () => {
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
router.push(`/email/new?loadDocuments=[${linkedDocument.value.id}]&bcc=${encodeURIComponent(currentTenant.value.standardEmailForInvoices)}`)
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(currentTenant.value.standardEmailForInvoices)}`)
} else {
router.push(`/email/new?loadDocuments=[${linkedDocument.value.id}]`)
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]`)
}
}
</script>
@@ -56,10 +64,10 @@ const openEmail = () => {
>
Bearbeiten
</UButton>
<UButton
<!-- <UButton
:to="dataStore.documents.find(i => i.createdDocument === itemInfo.id) ? dataStore.documents.find(i => i.createdDocument === itemInfo.id).url : ''"
target="_blank"
>In neuen Tab anzeigen</UButton>
>In neuen Tab anzeigen</UButton>-->
<UButton
@click="router.push(`/createDocument/edit/?linkedDocument=${itemInfo.id}`)"
>
@@ -71,13 +79,32 @@ const openEmail = () => {
>
E-Mail
</UButton>
<UButton
@click="router.push(`/createDocument/edit/?linkedDocument=${itemInfo.id}&loadMode=storno`)"
variant="outline"
color="rose"
>
Stornieren
</UButton>
<UButton
v-if="itemInfo.project"
@click="router.push(`/standardEntity/projects/show/${itemInfo.project}`)"
icon="i-heroicons-arrow-right-end-on-rectangle"
variant="outline"
>
Projekt
</UButton>
</template>
</UDashboardToolbar>
<UDashboardPanelContent>
<object
:data="linkedDocument.url"
class="h-full"
/>
</UDashboardPanelContent>
<object
:data="dataStore.documents.find(i => i.id === linkedDocument.id) ? dataStore.documents.find(i => i.id === linkedDocument.id).url : ''"
class="h-full"
/>

View File

@@ -1,369 +0,0 @@
<script setup>
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/customers")
},
'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: "",
infoData: {
country: "Deutschland"
},
active: true,
isCompany: true
})
const oldItemInfo = ref({})
//Functions
const setupPage = async () => {
if(mode.value === "show"){
itemInfo.value = await useSupabaseSelectSingle("customers",route.params.id,"*, contacts(*)")
} else if(mode.value === "edit") {
itemInfo.value = await useSupabaseSelectSingle("customers",route.params.id,"*")
}
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
}
const editItem = async () => {
await router.push(`/customers/edit/${itemInfo.value.id}`)
}
const setCityByZip = async () => {
itemInfo.value.infoData.city = await useZipCheck(itemInfo.value.infoData.zip)
}
setupPage()
</script>
<template>
<UDashboardNavbar
:title="itemInfo ? `Kunde: ${itemInfo.name}` : (mode === 'create' ? 'Kunde erstellen' : 'Kunde bearbeiten')"
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/customers`)"
>
Kunden
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium', ... itemInfo.active ? ['text-primary'] : ['text-rose-500']]"
>{{itemInfo ? `Kunde: ${itemInfo.name}` : (mode === 'create' ? 'Kunde erstellen' : 'Kunde bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('customers',itemInfo,oldItemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('customers',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="itemInfo ? router.push(`/customers/show/${itemInfo.id}`) : router.push(`/customers`)"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="editItem"
>
Bearbeiten
</UButton>
</template>
<template #badge v-if="itemInfo">
<UBadge
v-if="itemInfo.active"
>
Aktiv
</UBadge>
<UBadge
v-else
color="red"
>
Gesperrt
</UBadge>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UTabs
v-if="itemInfo.id && mode == 'show'"
:items="[{label: 'Informationen'},{label: 'Projekte'},{label: 'Objekte'},{label: 'Verträge'}]"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="flex mt-5">
<div class="w-1/2 mr-5">
<UCard >
<div class="text-wrap">
<p>Kundennummer: {{itemInfo.customerNumber}}</p>
<p>Typ: {{itemInfo.isCompany ? 'Firma' : 'Privatperson'}}</p>
<p v-if="itemInfo.infoData.street">Straße + Hausnummer: {{itemInfo.infoData.street}}</p>
<p v-if="itemInfo.infoData.zip && itemInfo.infoData.city">PLZ + Ort: {{itemInfo.infoData.zip}} {{itemInfo.infoData.city}}</p>
<p v-if="itemInfo.infoData.tel">Telefon: {{itemInfo.infoData.tel}}</p>
<p v-if="itemInfo.infoData.email">E-Mail: {{itemInfo.infoData.email}}</p>
<p v-if="itemInfo.infoData.web">Web: {{itemInfo.infoData.web}}</p>
<p v-if="itemInfo.infoData.ustid">USt-Id: {{itemInfo.infoData.ustid}}</p>
<p>Notizen:<br> {{itemInfo.notes}}</p>
</div>
</UCard>
<UCard class="mt-5">
<Toolbar>
<UButton
@click="router.push(`/contacts/create?customer=${itemInfo.id}`)"
>
+ Ansprechpartner
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getContactsByCustomerId(itemInfo.id)"
@select="(row) => router.push(`/contacts/show/${row.id}`)"
:columns="[{label: 'Anrede', key: 'salutation'},{label: 'Name', key: 'fullName'},{label: 'Rolle', key: 'role'}]"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Ansprechpartner' }"
>
</UTable>
</UCard>
</div>
<div class="w-1/2">
<UCard class="h-full">
<HistoryDisplay
type="customer"
v-if="itemInfo"
:element-id="itemInfo.id"
:render-headline="true"
/>
</UCard>
</div>
</div>
<UCard class="mt-5" v-else>
<div v-if="item.label === 'Projekte'">
<Toolbar>
<UButton
@click="router.push(`/projects/create?customer=${itemInfo.id}`)"
>
+ Projekt
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getProjectsByCustomerId(itemInfo.id)"
@select="(row) => router.push(`/projects/show/${row.id}`)"
:columns="[{label: 'Name', key: 'name'},{label: 'Phase', key: 'phase'}]"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Projekte' }"
>
<template #phase-data="{row}">
{{row.phases ? row.phases.find(i => i.active).label : ""}}
</template>
</UTable>
</div>
<div v-else-if="item.label === 'Objekte'">
<Toolbar>
<UButton
@click="router.push(`/plants/create?customer=${itemInfo.id}`)"
>
+ Objekt
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getPlantsByCustomerId(itemInfo.id)"
@select="(row) => router.push(`/plants/show/${row.id}`)"
:columns="[{label: 'Name', key: 'name'}]"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Objekte' }"
>
</UTable>
</div>
<div v-else-if="item.label === 'Verträge'">
<Toolbar>
<UButton
@click="router.push(`/contracts/create?customer=${itemInfo.id}`)"
>
+ Vertrag
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getContractsByCustomerId(itemInfo.id)"
@select="(row) => router.push(`/contracts/show/${row.id}`)"
:columns="[{label: 'Name', key: 'name'},{label: 'Aktiv', key: 'active'}]"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Verträge' }"
>
</UTable>
</div>
</UCard>
</template>
</UTabs>
<UForm v-else-if="mode === 'edit' || mode === 'create'" class="p-5">
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Kundennummer:"
>
<UInput
v-model="itemInfo.customerNumber"
placeholder="Leer lassen für automatisch generierte Nummer"
/>
</UFormGroup>
<UTooltip text="Ist ein Kunde nicht aktiv so wird er für neue Aufträge gesperrt">
<UFormGroup
label="Kunde aktiv:"
>
<UCheckbox
v-model="itemInfo.active"
/>
</UFormGroup>
</UTooltip>
<UFormGroup
label="Firmenkunde:"
>
<UCheckbox
v-model="itemInfo.isCompany"
/>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="itemInfo.notes"
/>
</UFormGroup>
<UFormGroup
label="Straße + Hausnummer"
>
<UInput
v-model="itemInfo.infoData.street"
/>
</UFormGroup>
<UFormGroup
label="Adresszusatz"
>
<UInput
v-model="itemInfo.infoData.special"
/>
</UFormGroup>
<UFormGroup
label="Postleitzahl"
>
<UInput
v-model="itemInfo.infoData.zip"
@focusout="setCityByZip"
/>
</UFormGroup>
<UFormGroup
label="Ort"
>
<UInput
v-model="itemInfo.infoData.city"
/>
</UFormGroup>
<UFormGroup
label="Land"
>
<USelectMenu
:options="['Deutschland','Niederlande','Belgien','Italien', 'Frankreich','Irland','USA','Spanien', 'Schweden']"
v-model="itemInfo.infoData.country"
/>
</UFormGroup>
<UFormGroup
label="Telefon:"
>
<UInput
v-model="itemInfo.infoData.tel"
/>
</UFormGroup>
<UFormGroup
label="E-Mail:"
>
<UInput
v-model="itemInfo.infoData.email"
/>
</UFormGroup>
<UFormGroup
label="Webseite:"
>
<UInput
v-model="itemInfo.infoData.web"
/>
</UFormGroup>
<UFormGroup
label="USt-Id:"
>
<UInput
v-model="itemInfo.infoData.ustid"
/>
</UFormGroup>
</UForm>
</UDashboardPanelContent>
</template>
<style scoped>
</style>

View File

@@ -1,168 +0,0 @@
<template>
<UDashboardNavbar title="Kunden" :badge="filteredRows.length">
<template #right>
<UInput
id="searchinput"
name="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(`/customers/create/`)">+ Kunde</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(`/customers/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Kunden 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 #isCompany-data="{row}">
<span v-if="row.isCompany">Firmenkunden</span>
<span v-else>Privatkunde</span>
</template>
<template #active-data="{row}">
<span v-if="row.active" class="text-primary-500">Aktiv</span>
<span v-else class="text-rose-500">Gesperrt</span>
</template>
<template #address-data="{row}">
{{row.infoData.street ? `${row.infoData.street}, ` : ''}}{{row.infoData.special ? `${row.infoData.special},` : ''}} {{row.infoData.zip ? row.infoData.zip : ""}} {{row.infoData.city ? `${row.infoData.city}, ` : ''}} {{row.infoData.country}}
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/customers/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/customers/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 supabase = useSupabaseClient()
const items = ref([])
const selectedItem = ref(0)
const setupPage = async () => {
items.value = await useSupabaseSelect("customers",null,"customerNumber")
}
setupPage()
const templateColumns = [
{
key: 'customerNumber',
label: "Kundennr.",
sortable: true
},
{
key: "name",
label: "Name",
sortable: true
},
{
key: "isCompany",
label: "Typ",
sortable: true
},
{
key: "notes",
label: "Notizen",
sortable: true
},
{
key: "active",
label: "Aktiv",
sortable: true
},
{
key: "address",
label: "Adresse",
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>

View File

@@ -1,580 +0,0 @@
<script setup>
import axios from 'axios'
import {sub} from 'date-fns'
const dataStore = useDataStore()
const accounts = ref([])
//accounts.value = dataStore.emailAccounts.map(i => { return { label: i.emailAddress, emailEngingeId: i.emailEngineId }})
const mailboxes = ref({})
const messages = ref([])
const selectedMailbox = ref("INBOX")
const selectedAccount = ref(null)
const selectedMessage = ref(null)
const setup = async () => {
accounts.value = dataStore.emailAccounts.map((i,index) => {
let item = { label: i.emailAddress, emailEngineId: i.emailEngineId }
if(index === 0) {
item.defaultOpen = true
}
return item
})
for await (const account of accounts.value) {
console.log(account.emailEngineId)
const {data,error} = await axios.get(`http://157.90.231.142:3000/v1/account/${account.emailEngineId}/mailboxes`, {headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'}})
console.log(data)
console.log(error)
mailboxes.value[account.emailEngineId] = data.mailboxes
}
}
const selectMailbox = async (account, mailbox) => {
selectedMailbox.value = mailbox
const {data,error} = await axios.get(`http://157.90.231.142:3000/v1/account/${account}/messages?path=${ mailbox.path}`, {headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'}})
console.log(data)
console.log(error)
messages.value = data.messages
selectedAccount.value = account
}
const messageHTML = ref(null)
const messageText = ref(null)
const selectMessage = async (account, message) => {
console.log(message)
selectedMessage.value = message
const {data,error} = await axios.get(`http://157.90.231.142:3000/v1/account/${account}/text/${message.text.id}`, {headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'}})
messageHTML.value = data.html
messageText.value = data.plain
}
const addFlags = async (account, message, flags) => {
console.log(flags)
const {data,error} = await axios({
method: "PUT",
url: `http://157.90.231.142:3000/v1/account/${account}/message/${message.id}`,
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
data: {
flags: {
add: flags
}
}
})
console.log(data)
console.log(error)
}
const removeFlags = async (account, message, flags) => {
console.log(flags)
const {data,error} = await axios({
method: "PUT",
url: `http://157.90.231.142:3000/v1/account/${account}/message/${message.id}`,
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
data: {
flags: {
delete: flags
}
}
})
console.log(data)
console.log(error)
}
const setSeen = async (seen,message) => {
if(seen) {
await addFlags(selectedAccount.value,message, ["\\Seen"])
await selectMailbox(selectedAccount.value, selectedMailbox.value)
} else {
await removeFlags(selectedAccount.value,message, ["\\Seen"])
await selectMailbox(selectedAccount.value, selectedMailbox.value)
}
}
const moveTo = async (destinationPath) => {
const {data,error} = await axios({
method: "PUT",
url: `http://157.90.231.142:3000/v1/account/${selectedAccount.value}/message/${selectedMessage.value.id}/move`,
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
data: {
path: destinationPath
}
})
console.log(data)
console.log(error)
selectedMessage.value = null
messageHTML.value = null
messageText.value = null
selectMailbox(selectedAccount.value, selectedMailbox.value)
}
const downloadAttachment = async (attachment) => {
const {data,error} = await axios({
method: "GET",
url: `http://157.90.231.142:3000/v1/account/${selectedAccount.value}/attachment/${attachment.id}`,
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
responseType: "blob"
})
const downloadURL = URL.createObjectURL(new Blob([data]))
const link = document.createElement('a')
link.href = downloadURL
link.setAttribute('download', attachment.filename)
document.body.appendChild(link)
link.click()
link.remove()
console.log(data)
console.log(error)
}
setup()
const selectedTab = ref(0)
const mails = ref([{
id: 1,
from: {
name: 'Alex Smith',
email: 'alex.smith@example.com',
avatar: {
src: 'https://i.pravatar.cc/128?u=1'
}
},
subject: 'Meeting Schedule',
body: 'Hi there, just a quick reminder about our meeting scheduled for 10 AM tomorrow. We\'ll be discussing the new marketing strategies and I would really appreciate your input on the matter. Looking forward to a productive session.',
date: new Date().toISOString()
}, {
id: 2,
unread: true,
from: {
name: 'Jordan Brown',
email: 'jordan.brown@example.com',
avatar: {
src: 'https://i.pravatar.cc/128?u=2'
}
},
subject: 'Project Update',
body: 'I wanted to provide you with the latest update on the project. We\'ve made significant progress on the development front and I\'ve attached a detailed report for your review. Please let me know your thoughts and any areas for improvement.',
date: sub(new Date(), { minutes: 7 }).toISOString()
}, {
id: 3,
unread: true,
from: {
name: 'Taylor Green',
email: 'taylor.green@example.com',
avatar: {
src: 'https://i.pravatar.cc/128?u=3'
}
},
subject: 'Lunch Plans',
body: 'Hey! I was wondering if you would like to grab lunch this Friday. I know a great spot downtown that serves the best Mexican cuisine. It would be a great opportunity for us to catch up and discuss the upcoming team event.',
date: sub(new Date(), { hours: 3 }).toISOString()
}, {
id: 4,
from: {
name: 'Morgan White',
email: 'morgan.white@example.com',
avatar: {
src: 'https://i.pravatar.cc/128?u=4'
}
},
subject: 'New Proposal',
body: 'I\'ve attached the new proposal for our next project. It outlines all the objectives, timelines, and resource allocations. I\'m particularly excited about the innovative approach we\'re taking this time. Please have a look and let me know your thoughts.',
date: sub(new Date(), { days: 1 }).toISOString()
}, {
id: 5,
from: {
name: 'Casey Gray',
email: 'casey.gray@example.com'
},
subject: 'Travel Itinerary',
body: 'Your travel itinerary for the upcoming business trip is ready. I\'ve included all flight details, hotel reservations, and meeting schedules. Please review and let me know if there are any changes you would like to make or any additional arrangements needed.',
date: sub(new Date(), { days: 1 }).toISOString()
}, {
id: 6,
from: {
name: 'Jamie Johnson',
email: 'jamie.johnson@example.com'
},
subject: 'Budget Report',
body: 'I\'ve completed the budget report for this quarter. It includes a detailed analysis of our expenditures and revenue, along with projections for the next quarter. I believe there are some areas where we can optimize our spending. Let\'s discuss this in our next finance meeting.',
date: sub(new Date(), { days: 2 }).toISOString()
}, {
id: 7,
from: {
name: 'Riley Davis',
email: 'riley.davis@example.com',
avatar: {
src: 'https://i.pravatar.cc/128?u=7'
}
},
subject: 'Training Session',
body: 'Just a reminder about the training session scheduled for next week. We\'ll be covering new software tools that are crucial for our workflow. It\'s important that everyone attends as this will greatly enhance our team\'s efficiency. Please confirm your availability.',
date: sub(new Date(), { days: 2 }).toISOString()
}, {
id: 8,
unread: true,
from: {
name: 'Kelly Wilson',
email: 'kelly.wilson@example.com',
avatar: {
src: 'https://i.pravatar.cc/128?u=8'
}
},
subject: 'Happy Birthday!',
body: 'Happy Birthday! Wishing you a fantastic day filled with joy and laughter. Your dedication and hard work throughout the year have been invaluable to our team. Enjoy your day to the fullest!',
date: sub(new Date(), { days: 2 }).toISOString()
}, {
id: 9,
from: {
name: 'Drew Moore',
email: 'drew.moore@example.com'
},
subject: 'Website Feedback',
body: 'We are in the process of revamping our company website and I would greatly appreciate your feedback on the new design. Your perspective is always insightful and could help us enhance the user experience significantly. Please let me know a convenient time for you to discuss this.',
date: sub(new Date(), { days: 5 }).toISOString()
}, {
id: 10,
from: {
name: 'Jordan Taylor',
email: 'jordan.taylor@example.com'
},
subject: 'Gym Membership',
body: 'This is a friendly reminder that your gym membership is due for renewal at the end of this month. We\'ve added several new classes and facilities that I think you\'ll really enjoy. Let me know if you would like a tour of the new facilities.',
date: sub(new Date(), { days: 5 }).toISOString()
}, {
id: 11,
unread: true,
from: {
name: 'Morgan Anderson',
email: 'morgan.anderson@example.com'
},
subject: 'Insurance Policy',
body: 'I\'m writing to inform you that your insurance policy details have been updated. The new document outlines the changes in coverage and premium rates. It\'s important to review these changes to ensure they meet your needs. Please don\'t hesitate to contact me if you have any questions.',
date: sub(new Date(), { days: 12 }).toISOString()
}, {
id: 12,
from: {
name: 'Casey Thomas',
email: 'casey.thomas@example.com'
},
subject: 'Book Club Meeting',
body: 'I\'m excited to remind you about our next book club meeting scheduled for next Thursday. We\'ll be discussing \'The Great Gatsby,\' and I\'m looking forward to hearing everyone\'s perspectives. Also, we will be choosing our next book, so bring your suggestions!',
date: sub(new Date(), { months: 1 }).toISOString()
}, {
id: 13,
from: {
name: 'Jamie Jackson',
email: 'jamie.jackson@example.com'
},
subject: 'Recipe Exchange',
body: 'Don\'t forget to send in your favorite recipe for our upcoming recipe exchange. It\'s a great opportunity to share and discover new and delicious meals. I\'m particularly excited to try out new dishes and add some variety to my cooking.',
date: sub(new Date(), { months: 1 }).toISOString()
}, {
id: 14,
from: {
name: 'Riley White',
email: 'riley.white@example.com'
},
subject: 'Yoga Class Schedule',
body: 'The new schedule for yoga classes is now available. We\'ve added some new styles and adjusted the timings to accommodate more participants. I believe these classes are a great way to relieve stress and stay healthy. Hope to see you there!',
date: sub(new Date(), { months: 1 }).toISOString()
}, {
id: 15,
from: {
name: 'Kelly Harris',
email: 'kelly.harris@example.com'
},
subject: 'Book Launch Event',
body: 'I\'m thrilled to invite you to my book launch event next month. It\'s been a journey writing this book and I\'m eager to share it with you. The event will include a reading session, Q&A, and a signing opportunity. Your support would mean a lot to me.',
date: sub(new Date(), { months: 1 }).toISOString()
}, {
id: 16,
from: {
name: 'Drew Martin',
email: 'drew.martin@example.com'
},
subject: 'Tech Conference',
body: 'Join us at the upcoming tech conference where we will be discussing the latest trends and innovations in technology. This is a great opportunity to network with industry leaders and learn about cutting-edge developments. Your participation would greatly contribute to our team\'s knowledge and growth.',
date: sub(new Date(), { months: 1, days: 4 }).toISOString()
}, {
id: 17,
from: {
name: 'Alex Thompson',
email: 'alex.thompson@example.com'
},
subject: 'Art Exhibition',
body: 'I wanted to invite you to check out the new art exhibition this weekend. It features some amazing contemporary artists and their latest works. It\'s a great opportunity to immerse yourself in the local art scene and get inspired. Let me know if you\'re interested in going together.',
date: sub(new Date(), { months: 1, days: 15 }).toISOString()
}, {
id: 18,
from: {
name: 'Jordan Garcia',
email: 'jordan.garcia@example.com'
},
subject: 'Networking Event',
body: 'I\'m looking forward to seeing you at the networking event next week. It\'s a great chance to connect with professionals from various industries and expand our professional network. There will also be guest speakers discussing key business trends. Your presence would add great value to the discussions.',
date: sub(new Date(), { months: 1, days: 18 }).toISOString()
}, {
id: 19,
from: {
name: 'Taylor Rodriguez',
email: 'taylor.rodriguez@example.com'
},
subject: 'Volunteer Opportunity',
body: 'We\'re looking for volunteers for the upcoming community event. It\'s a great opportunity to give back and make a positive impact. There are various roles available, so you can choose something that aligns with your interests and skills. Let me know if you\'re interested and I\'ll provide more details.',
date: sub(new Date(), { months: 1, days: 25 }).toISOString()
}, {
id: 20,
from: {
name: 'Morgan Lopez',
email: 'morgan.lopez@example.com'
},
subject: 'Car Service Reminder',
body: 'Just a reminder that your car is due for service next week. Regular maintenance is important to ensure your vehicle\'s longevity and performance. I\'ve included the details of the service center and the recommended services in this email. Feel free to contact them directly to schedule an appointment.',
date: sub(new Date(), { months: 2 }).toISOString()
}])
const dropdownItems = [[{
label: 'Mark as unread',
icon: 'i-heroicons-check-circle'
}, {
label: 'Mark as important',
icon: 'i-heroicons-exclamation-circle'
}], [{
label: 'Star thread',
icon: 'i-heroicons-star'
}, {
label: 'Mute thread',
icon: 'i-heroicons-pause-circle'
}]]
// Filter mails based on the selected tab
const filteredMails = computed(() => {
if (selectedTab.value === 1) {
return mails.value.filter(mail => !!mail.unread)
} else {
return mails.value
}
})
const selectedMail = ref(null)
const isMailPanelOpen = computed({
get () {
return !!selectedMail.value
},
set (value) {
if (!value) {
selectedMail.value = null
}
}
})
</script>
<template>
<!-- <div class="flex flex-row">
<div id="mailboxlist">
<UAccordion
:items="accounts"
>
<template #default="{ item, index, open }">
<UButton variant="soft" class="mt-3">
<span class="truncate">{{ item.label }}</span>
<template #trailing>
<UIcon
name="i-heroicons-chevron-right-20-solid"
class="w-5 h-5 ms-auto transform transition-transform duration-200"
:class="[open && 'rotate-90']"
/>
</template>
</UButton>
</template>
<template #item="{ item }">
<div
v-for="mailbox in mailboxes[item.emailEngineId]"
class="my-3"
>
<UButton
@click="selectMailbox(item.emailEngineId, mailbox)"
variant="outline"
>
<span v-if="mailbox.name === 'Trash'">Papierkorb</span>
<span v-else-if="mailbox.name === 'INBOX'">Eingang</span>
<span v-else-if="mailbox.name === 'Sent'">Gesendet</span>
<span v-else-if="mailbox.name === 'Drafts'">Entwürfe</span>
<span v-else-if="mailbox.name === 'spambucket'">Spam</span>
<span v-else>{{mailbox.name}}</span>
<UBadge v-if="mailbox.messages > 0">{{mailbox.messages}}</UBadge>
</UButton>
</div>
</template>
</UAccordion>
</div>
<UDivider orientation="vertical" class="maiLDivider"/>
<div id="maillist">
<div
v-for="message in messages"
v-if="messages.length > 0"
>
<div
:class="message === selectedMessage ? ['message','text-primary-500'] : ['message']"
@click="selectMessage(selectedAccount, message),
!message.flags.includes('\\Seen') ? setSeen(true,message) : null"
>
<UChip
position="top-left"
:show="!message.flags.includes('\\Seen')"
>
<h1>{{message.from.name ||message.from.address}}</h1>
</UChip>
<h3>{{message.subject}}</h3>
</div>
<UDivider class="my-3"/>
</div>
<div
v-else
>
Keine E-Mails in diesem Postfach
</div>
</div>
<UDivider orientation="vertical" class="maiLDivider"/>
<div id="mailcontent" v-if="selectedMessage">
<Toolbar>
&lt;!&ndash;<UButton
@click="setup"
>Setup</UButton>
<UButton>+ Neu</UButton>
<UButton>Sync</UButton>
<UButton>Papierkorb</UButton>
<UButton>Weiterleiten</UButton>
<UButton>Antworten</UButton>&ndash;&gt;
<UButton
@click="setSeen(false,selectedMessage)"
>
Als Ungelesen markieren
</UButton>
<UButton
@click="moveTo('INBOX.Trash')"
icon="i-heroicons-trash"
>
</UButton>
</Toolbar>
<UAlert
v-if="selectedMessage"
:title="attachment.filename"
v-for="attachment in selectedMessage.attachments"
class="my-3"
:actions="[{label: 'Download', click:() => {downloadAttachment(attachment)}}]"
>
</UAlert>
<iframe
v-if="messageHTML"
style="width: 100%; height: 100%"
:srcdoc="messageHTML">
</iframe>
<pre
class="text-wrap"
v-else-if="messageText">
{{messageText}}
</pre>
</div>
</div>-->
<UDashboardPanel id="inbox" :width="400" :resizable="{ min: 300, max: 500 }">
<UDashboardNavbar title="Inbox" >
<template #right>
<UTabs v-model="selectedTab" :items="[{label: 'Alle'},{label: 'Ungelesen'}]" :ui="{ wrapper: '', list: { height: 'h-9', tab: { height: 'h-7', size: 'text-[13px]' } } }" />
</template>
</UDashboardNavbar>
<!-- ~/components/inbox/InboxList.vue -->
<InboxList v-model="selectedMail" :mails="filteredMails" />
</UDashboardPanel>
<UDashboardPanel v-model="isMailPanelOpen" collapsible grow side="right">
<template v-if="selectedMail">
<UDashboardNavbar>
<template #toggle>
<UDashboardNavbarToggle icon="i-heroicons-x-mark" />
<UDivider orientation="vertical" class="mx-1.5 lg:hidden" />
</template>
<template #left>
<UTooltip text="Archive">
<UButton icon="i-heroicons-archive-box" color="gray" variant="ghost" />
</UTooltip>
<UTooltip text="Move to junk">
<UButton icon="i-heroicons-archive-box-x-mark" color="gray" variant="ghost" />
</UTooltip>
<!-- <UDivider orientation="vertical" class="mx-1.5" />-->
<!-- <UPopover :popper="{ placement: 'bottom-start' }">
<template #default="{ open }">
<UTooltip text="Snooze" :prevent="open">
<UButton icon="i-heroicons-clock" color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" />
</UTooltip>
</template>
<template #panel="{ close }">
<DatePicker @close="close" />
</template>
</UPopover>-->
</template>
<template #right>
<UTooltip text="Reply">
<UButton icon="i-heroicons-arrow-uturn-left" color="gray" variant="ghost" />
</UTooltip>
<UTooltip text="Forward">
<UButton icon="i-heroicons-arrow-uturn-right" color="gray" variant="ghost" />
</UTooltip>
<UDivider orientation="vertical" class="mx-1.5" />
<UDropdown :items="dropdownItems">
<UButton icon="i-heroicons-ellipsis-vertical" color="gray" variant="ghost" />
</UDropdown>
</template>
</UDashboardNavbar>
<!-- ~/components/inbox/InboxMail.vue -->
<InboxMail :mail="selectedMail" />
</template>
<div v-else class="flex-1 hidden lg:flex items-center justify-center">
<UIcon name="i-heroicons-inbox" class="w-32 h-32 text-gray-400 dark:text-gray-500" />
</div>
</UDashboardPanel>
</template>
<style scoped>
</style>

View File

@@ -1,369 +0,0 @@
<script setup>
import {BlobReader, BlobWriter, ZipWriter} from "@zip.js/zip.js";
import {useSupabaseSelectDocuments} from "~/composables/useSupabase.js";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const user = useSupabaseUser()
const toast = useToast()
dataStore.fetchDocuments()
const uploadModalOpen = ref(false)
const uploadInProgress = ref(false)
const fileUploadFormData = ref({
tags: ["Eingang"],
path: "",
tenant: dataStore.currentTenant
})
let tags = dataStore.getDocumentTags
const selectedTags = ref("Eingang")
const documents = ref([])
const folders = ref([])
const selectedPath = ref("_")
const loadingDocs = ref(false)
const isDragTarget = ref(false)
const setupPage = async () => {
//documents.value = await useSupabaseSelectDocuments("*, project(id,name), customer(id,name), contract(id,name), vendor(id,name), plant(id,name), vehicle(id,licensePlate), product(id,name), profile(id,fullName) ")
//documents.value = await useSupabaseSelectDocuments("*",null, selectedPath.value)
//console.log(documents.value)
folders.value = dataStore.ownTenant.documentFolders
documents.value = await useSupabaseSelectDocuments("*",null,selectedPath.value)
//await supabase.from("documents").select().eq("folderPath",selectedPath.value).eq("tenant",dataStore.currentTenant)
//console.log(await supabase.from("documents").select().eq("folderPath",selectedPath.value))
const dropZone = document.getElementById("drop_zone")
dropZone.ondragover = function (event) {
console.log(event)
isDragTarget.value = true
event.preventDefault()
}
dropZone.ondragleave = function (event) {
isDragTarget.value = false
}
dropZone.ondrop = async function (event) {
console.log("files dropped")
event.preventDefault()
await uploadFiles(event.dataTransfer.files)
isDragTarget.value = false
setupPage()
}
loadingDocs.value = false
}
setupPage()
const currentFolders = computed(() => {
if(folders.value.length > 0) {
/*console.log(folders.value[0].path.split("/").filter(x => x.length > 0))
console.log(selectedPath.value.split("/").filter(x => x.length > 0))*/
let tempFolders = folders.value.filter(i => (i.path.split("_").filter(x => x.length > 0) || []).length === selectedPath.value.split("_").filter(x => x.length > 0).length + 1)
tempFolders = tempFolders.filter(i => i.path.includes(selectedPath.value))
return tempFolders
}
})
const breadcrumbLinks = computed(() => {
return [{
label: "Home",
click: () => {
changePath("_")
},
icon: "i-heroicons-folder"
},...selectedPath.value.split("_").filter(x => x.length > 0).map((i,index) => {
let re = new RegExp(".+?" + i )
let path = selectedPath.value.match(re)[0]
return {
label: folders.value.find(x => x.path === path).name ||path,
click: () => {
changePath(path)
},
icon: "i-heroicons-folder"
}
})]
})
const filteredDocuments = computed(() => {
/*if(selectedTags.value !== "Archiviert") {
return documents.value.filter(i => i.tags.find(t => selectedTags.value === t) && !i.tags.includes("Archiviert"))
} else {
return documents.value.filter(i => i.tags.find(t => selectedTags.value === t))
}*/
return documents.value
})
const changePath = async (newPath) => {
loadingDocs.value = true
selectedPath.value = newPath
setupPage()
}
const uploadFiles = async (files) => {
uploadInProgress.value = true;
if(files) {
await dataStore.uploadFiles({tags: ["Ablage"],tenant: dataStore.currentTenant, folderPath: selectedPath.value}, files, true)
} else {
await dataStore.uploadFiles(fileUploadFormData.value, document.getElementById("fileUploadInput").files, true)
}
uploadModalOpen.value = false;
uploadInProgress.value = false;
}
const downloadSelected = async () => {
const bucket = "files";
let files = []
dataStore.documents.filter(doc => doc.selected).forEach(doc => files.push(doc.path))
console.log(files)
// If there are no files in the folder, throw an error
if (!files || !files.length) {
throw new Error("No files to download");
}
const promises = [];
// Download each file in the folder
files.forEach((file) => {
promises.push(
supabase.storage.from(bucket).download(`${file}`)
);
});
// Wait for all the files to download
const response = await Promise.allSettled(promises);
// Map the response to an array of objects containing the file name and blob
const downloadedFiles = response.map((result, index) => {
if (result.status === "fulfilled") {
console.log(files[index].split("/")[files[index].split("/").length -1])
return {
name: files[index].split("/")[files[index].split("/").length -1],
blob: result.value.data,
};
}
});
// Create a new zip file
const zipFileWriter = new BlobWriter("application/zip");
const zipWriter = new ZipWriter(zipFileWriter, { bufferedWrite: true });
// Add each file to the zip file
downloadedFiles.forEach((downloadedFile) => {
if (downloadedFile) {
zipWriter.add(downloadedFile.name, new BlobReader(downloadedFile.blob));
}
});
// Download the zip file
const url = URL.createObjectURL(await zipWriter.close());
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "documents.zip");
document.body.appendChild(link);
link.click();
}
</script>
<template>
<UDashboardNavbar
title="Dokumente"
>
</UDashboardNavbar>
<!-- <UDashboardToolbar>
<template #right>
<UButton @click="uploadModalOpen = true">Hochladen</UButton>
&lt;!&ndash; <UButton
@click="downloadSelected"
:disabled="dataStore.documents.filter(doc => doc.selected).length === 0"
>Herunterladen</UButton>&ndash;&gt;
</template>
&lt;!&ndash; <template #right>
<USelectMenu
:options="tags"
v-model="selectedTags"
class="w-40"
>
<template #label>
{{selectedTags}}
</template>
</USelectMenu>
</template>&ndash;&gt;
</UDashboardToolbar>-->
<UDashboardToolbar>
<template #left>
<UBreadcrumb
:links="breadcrumbLinks"
/>
</template>
<template #right>
<UButton @click="uploadModalOpen = true">Hochladen</UButton>
</template>
</UDashboardToolbar>
<div id="drop_zone" class="h-full scrollList">
<UDashboardPanelContent v-if="!isDragTarget" >
<div class="flex flex-row w-full flex-wrap" v-if="currentFolders.length > 0">
<a
class="w-1/6 folderIcon flex flex-col p-5 m-2"
v-for="folder in currentFolders"
@click="changePath(folder.path)"
>
<UIcon
name="i-heroicons-folder"
class="w-20 h-20"
/>
<span class="text-center truncate">{{folder.name}}</span>
</a>
</div>
<UDivider class="my-5" v-if="currentFolders.length > 0">Dokumente</UDivider>
<div v-if="!loadingDocs">
<DocumentList
v-if="filteredDocuments.length > 0"
:documents="filteredDocuments"
@selectDocument="(info) => console.log(info)"
/>
<UAlert
v-else
class="mt-5 w-1/2 mx-auto"
icon="i-heroicons-light-bulb"
title="Keine Dokumente vorhanden"
color="primary"
variant="outline"
/>
</div>
<UProgress
animation="carousel"
v-else
class="w-2/3 my-5 mx-auto"
/>
</UDashboardPanelContent>
<UCard
class=" m-5"
v-else>
<template #header>
<p class="mx-auto">Dateien zum hochladen hierher ziehen</p>
</template>
</UCard>
</div>
<USlideover
v-model="uploadModalOpen"
>
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
Datei Hochladen
</template>
<div class="h-full">
<UFormGroup
label="Datei:"
>
<UInput
type="file"
id="fileUploadInput"
multiple
/>
</UFormGroup>
<UFormGroup
label="Tags:"
class="mt-3"
>
<USelectMenu
multiple
searchable
searchable-placeholder="Suchen..."
:options="tags"
v-model="fileUploadFormData.tags"
/>
</UFormGroup>
</div>
<template #footer>
<UButton
v-if="!uploadInProgress"
class="mt-3"
@click="uploadFiles"
>Hochladen</UButton>
<UProgress
v-else
animation="carousel"
/>
</template>
</UCard>
</USlideover>
</template>
<style scoped>
.folderIcon {
border: 1px solid lightgrey;
border-radius: 10px;
color: dimgrey;
}
.folderIcon:hover {
border: 1px solid #69c350;
color: #69c350;
}
</style>

View File

@@ -1,11 +0,0 @@
<script setup>
</script>
<template>
<tiptap/>
</template>
<style scoped>
</style>

View File

@@ -2,6 +2,7 @@
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const selectedTab = ref(0)
@@ -12,7 +13,7 @@ const accountData = ref(null)
const availableAccounts = ref(null)
const selectedAccount = ref(null)
const setupPage = async () => {
availableAccounts.value = (await supabase.from("emailAccounts").select("*").contains("profiles",[dataStore.activeProfile.id])).data
availableAccounts.value = (await supabase.from("emailAccounts").select("*").contains("profiles",[profileStore.activeProfile.id])).data
console.log(availableAccounts.value)
if(availableAccounts.value.length > 0) {

View File

@@ -2,6 +2,7 @@
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
@@ -29,7 +30,7 @@ const setupPage = async () => {
} else {
emailData.value.account = emailAccounts.value[0].id
preloadedContent.value = `<p></p><p></p><p></p>${dataStore.activeProfile.emailSignature}`
preloadedContent.value = `<p></p><p></p><p></p>${profileStore.activeProfile.emailSignature || ""}`
//Check Query
if(route.query.to) emailData.value.to = route.query.to
@@ -39,18 +40,28 @@ const setupPage = async () => {
if(route.query.loadDocuments) {
//console.log(JSON.parse(route.query.loadDocuments))
const {data,error} = await supabase.from("documents").select('*, createdDocument(id,documentNumber,title,contact(email))').in('id',JSON.parse(route.query.loadDocuments))
console.log(JSON.parse(route.query.loadDocuments))
const data = await useFiles().selectSomeDocuments(JSON.parse(route.query.loadDocuments))
console.log(data)
if(error) console.log(error)
if(data) loadedDocuments.value = data
//console.log(loadedDocuments.value)
if(loadedDocuments.value.length > 0) {
emailData.value.subject = loadedDocuments.value[0].createdDocument.title
emailData.value.to = loadedDocuments.value[0].createdDocument.contact ? loadedDocuments.value[0].createdDocument.contact.email : ""
console.log(loadedDocuments.value[0])
emailData.value.subject = loadedDocuments.value[0].createddocument.title
if(loadedDocuments.value[0].createddocument.contact && loadedDocuments.value[0].createddocument.contact.email) {
console.log("Contact")
emailData.value.to = loadedDocuments.value[0].createddocument.contact.email
} else if(loadedDocuments.value[0].createddocument.customer && loadedDocuments.value[0].createddocument.customer.infoData.email) {
emailData.value.to = loadedDocuments.value[0].createddocument.customer.infoData.email
}
}
}
@@ -121,7 +132,7 @@ const sendEmail = async () => {
for await (const doc of loadedDocuments.value) {
const {data,error} = await supabase.storage.from("files").download(doc.path)
const {data,error} = await supabase.storage.from("filesdev").download(doc.path)
body.attachments.push({
filename: doc.path.split("/")[doc.path.split("/").length -1],
@@ -191,30 +202,39 @@ const sendEmail = async () => {
/>
</UFormGroup>
<UDivider class="my-3"/>
<UInput
class="w-full my-1"
placeholder="Empfänger"
variant="ghost"
v-model="emailData.to"
/>
<UInput
class="w-full my-1"
placeholder="Kopie"
variant="ghost"
v-model="emailData.cc"
/>
<UInput
class="w-full my-1"
placeholder="Blindkopie"
variant="ghost"
v-model="emailData.bcc"
/>
<UInput
placeholder="Betreff"
class="w-full my-1"
variant="ghost"
v-model="emailData.subject"
/>
<UFormGroup
label="Empfänger"
>
<UInput
class="w-full my-1"
v-model="emailData.to"
/>
</UFormGroup>
<UFormGroup
label="Kopie"
>
<UInput
class="w-full my-1"
v-model="emailData.cc"
/>
</UFormGroup>
<UFormGroup
label="Blindkopie"
>
<UInput
class="w-full my-1"
placeholder=""
v-model="emailData.bcc"
/>
</UFormGroup>
<UFormGroup
label="Betreff"
>
<UInput
class="w-full my-1"
v-model="emailData.subject"
/>
</UFormGroup>
</div>
</UDashboardToolbar>
@@ -227,7 +247,7 @@ const sendEmail = async () => {
class="mx-auto w-10 h-10"
/>
<span class="text-center text-2xl">Anhänge hochladen</span>-->
<input
<UInput
id="inputAttachments"
type="file"
multiple
@@ -242,13 +262,14 @@ const sendEmail = async () => {
class="list-disc"
v-for="doc in loadedDocuments"
>
<span v-if="doc.createdDocument">Dokument - {{doc.createdDocument.documentNumber}}</span>
<span v-if="doc.createddocument">Dokument - {{doc.createddocument.documentNumber}}</span>
</li>
</ul>
</div>
<Tiptap
class="mt-3"
@updateContent="contentChanged"
:preloadedContent="preloadedContent"
/>

View File

@@ -9,6 +9,7 @@ definePageMeta({
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const user = useSupabaseUser()
const toast = useToast()
@@ -16,22 +17,29 @@ const toast = useToast()
const timeTypes = dataStore.getTimeTypes
const timeInfo = ref({
user: "",
start: "",
end: null,
profile: "",
startDate: "",
endDate: null,
notes: null,
projectId: null,
project: null,
type: null
})
const filterUser = ref(user.value.id || "")
const filterUser = ref(profileStore.activeProfile.id || "")
const times = ref([])
const setup = async () => {
times.value = await useSupabaseSelect("times","*, profile(*)")
}
setup()
const filteredRows = computed(() => {
let times = dataStore.times
//let times = times.value
if(dataStore.hasRight('viewTimes')) {
/*if(dataStore.hasRight('viewTimes')) {
if(filterUser.value !== "") {
times = times.filter(i => i.user === filterUser.value)
}
@@ -39,9 +47,9 @@ const filteredRows = computed(() => {
times = times.filter(i => i.user === user.value.id)
} else {
times = []
}
}*/
return times
return times.value
})
@@ -62,42 +70,34 @@ const columns = [
{
key:"state",
label: "Status",
sortable:true
},
{
key: "user",
label: "Benutzer",
sortable:true
},
{
key:"start",
key:"startDate",
label:"Start",
sortable:true
},
{
key: "endDate",
label: "Ende",
},
{
key:"type",
label:"Typ",
sortable:true
},
{
key: "end",
label: "Ende",
sortable:true
},
{
key: "duration",
label: "Dauer",
sortable:true
},
{
key: "projectId",
key: "project",
label: "Projekt",
sortable:true
},
{
key: "notes",
label: "Notizen",
sortable:true
}
]
@@ -109,9 +109,9 @@ const configTimeMode = ref("create")
const startTime = async () => {
console.log("started")
timeInfo.value.user = user.value.id
timeInfo.value.profile = profileStore.activeProfile.id
timeInfo.value.start = new Date().toISOString()
timeInfo.value.tenant = dataStore.currentTenant
timeInfo.value.tenant = profileStore.currentTenant
const {data,error} = await supabase
.from("times")
@@ -123,7 +123,7 @@ const startTime = async () => {
} else if(data) {
//timeInfo.value = data[0]
await dataStore.fetchTimes()
runningTimeInfo.value = dataStore.times.find(time => time.user === user.value.id && !time.end)
runningTimeInfo.value = dataStore.times.find(time => time.profile === profileStore.activeProfile.id && !time.end)
}
}
@@ -154,15 +154,15 @@ const stopStartedTime = async () => {
}
}
if(dataStore.times.find(time => time.user == user.value.id && !time.end)) {
runningTimeInfo.value = dataStore.times.find(time => time.user == user.value.id && !time.end)
if(times.value.find(time => time.profile == profileStore.activeProfile.id && !time.end)) {
runningTimeInfo.value = times.value.find(time => time.profile == profileStore.activeProfile.id && !time.end)
}
const createTime = async () => {
const {data,error} = await supabase
.from("times")
.insert({...itemInfo.value, tenant: dataStore.currentTenant})
.insert({...itemInfo.value, tenant: profileStore.currentTenant})
.select()
if(error) {
@@ -240,14 +240,13 @@ const setState = async (newState) => {
Erstellen
</UButton>
<USelectMenu
v-if="dataStore.hasRight('viewTimes')"
:options="dataStore.profiles"
:options="profileStore.profiles"
option-attribute="fullName"
value-attribute="id"
v-model="filterUser"
>
<template #label>
{{dataStore.getProfileById(filterUser) ? dataStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
{{profileStore.getProfileById(filterUser) ? profileStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
</template>
</USelectMenu>
</template>
@@ -339,14 +338,14 @@ const setState = async (newState) => {
label="Benutzer:"
>
<USelectMenu
:options="dataStore.profiles"
:options="profileStore.profiles"
v-model="itemInfo.user"
option-attribute="fullName"
value-attribute="id"
:disabled="(configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf') || (!dataStore.hasRight('createTime') || !dataStore.hasRight('createOwnTime'))"
>
<template #label>
{{dataStore.profiles.find(profile => profile.id === itemInfo.user) ? dataStore.profiles.find(profile => profile.id === itemInfo.user).fullName : "Benutzer auswählen"}}
{{profileStore.profiles.find(profile => profile.id === itemInfo.user) ? profileStore.profiles.find(profile => profile.id === itemInfo.user).fullName : "Benutzer auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
@@ -444,7 +443,7 @@ const setState = async (newState) => {
>{{row.state}}</span>
</template>
<template #user-data="{row}">
{{dataStore.profiles.find(profile => profile.id === row.user) ? dataStore.profiles.find(profile => profile.id === row.user).fullName : row.user }}
{{profileStore.profiles.find(profile => profile.id === row.user) ? profileStore.profiles.find(profile => profile.id === row.user).fullName : row.user }}
</template>
<template #start-data="{row}">

View File

@@ -1,259 +0,0 @@
<script setup>
import dayjs from "dayjs";
const route = useRoute()
const router = useRouter()
const dataStore = useDataStore()
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
resources: []
})
const oldItemInfo = ref({})
const resourceToAdd = ref(dataStore.activeProfile.id)
/*const mapResources = () => {
console.log(itemInfo.value.resources)
itemInfo.value.resources.map(resource => {
console.log(resource)
return {
id: resource.id,
type: resource.type
}
})
}*/
const setupPage = async () => {
if(mode.value === "show"){
itemInfo.value = await useSupabaseSelectSingle("events",route.params.id,"*, project(id,name)")
} else if(mode.value === "edit" && route.params.id) {
itemInfo.value = await useSupabaseSelectSingle("events",route.params.id,"*")
}
if(route.query.start) itemInfo.value.start = new Date(route.query.start.replace(" ", "+"))
if(route.query.end) itemInfo.value.end = new Date(route.query.end.replace(" ", "+"))
if(route.query.resources) {
itemInfo.value.resources = JSON.parse(route.query.resources).map(resource => {
return dataStore.getResourcesList.find(i => i.id === resource)
})
}
if(route.query.project) itemInfo.value.project = route.query.project
if(itemInfo.value.id) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="mode === 'show' ? 'Termin: ' + itemInfo.title : 'Neuen Termin erstellen'">
<template #right>
<UButton
color="rose"
@click="router.push(route.params.id ? `/events/show/${route.params.id}` : `/events`)"
v-if="mode === 'edit'"
>
Abbrechen
</UButton>
<UButton
@click="dataStore.createNewItem('events',itemInfo)"
v-if="mode === 'edit' && !route.params.id"
>
Erstellen
</UButton>
<UButton
@click="dataStore.updateItem('events',itemInfo,oldItemInfo)"
v-else-if="mode === 'edit' && route.params.id"
>
Speichern
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/events/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<!-- <UDashboardToolbar>
</UDashboardToolbar>-->
<UTabs
v-if="mode === 'show'"
:items="[{label:'Informationen'},{label:'Logbuch'}]"
class="p-5"
>
<template #item="{item}">
<UCard class="mt-5">
<div v-if="item.label === 'Informationen'">
<div class="truncate" >
<p>Titel: {{itemInfo.title ? itemInfo.title : ''}}</p>
<p>Typ: {{itemInfo.type? itemInfo.type : ''}}</p>
<p>Start: {{dayjs(itemInfo.start).format("DD.MM.YY HH:mm")}}</p>
<p>Ende: {{dayjs(itemInfo.end).format("DD.MM.YY HH:mm")}}</p>
<p>Projekt: {{itemInfo.project ? itemInfo.project.name : ''}}</p>
<p>Link: <a v-if="itemInfo.link" :href="itemInfo.link">{{itemInfo.link }}</a></p>
<p>Resources: {{itemInfo.resources.map((x) => `${x.type} - ${x.title}`).join(",")}}</p>
<p>Notizen: {{itemInfo.notes ? itemInfo.notes : ''}}</p>
</div>
</div>
<div v-if="item.label === 'Logbuch'">
<HistoryDisplay
type="event"
v-if="itemInfo"
:element-id="itemInfo.id"
/>
</div>
</UCard>
</template>
</UTabs>
<UForm class="p-5" v-if="mode === 'edit'">
<UFormGroup
label="Titel:"
>
<UInput
v-model="itemInfo.title"
/>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<USelectMenu
v-model="itemInfo.project"
:options="dataStore.projects"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche..."
:search-attributes="['name']"
>
<template #label>
{{dataStore.getProjectById(itemInfo.project) ? dataStore.getProjectById(itemInfo.project).name : "Kein Projekt ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<USelectMenu
v-model="itemInfo.type"
:options="dataStore.getEventTypes"
option-attribute="label"
value-attribute="label"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Link:"
>
<UInput
v-model="itemInfo.link"
/>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="itemInfo.notes"
rows="3"
/>
</UFormGroup>
<UFormGroup
label="Start:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
variant="outline"
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.start ? dayjs(itemInfo.start).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
/>
<template #panel="{ close }">
<LazyDatePicker
v-model="itemInfo.start"
mode="dateTime"
/>
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Ende:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
variant="outline"
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.end ? dayjs(itemInfo.end).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
/>
<template #panel="{ close }">
<LazyDatePicker
v-model="itemInfo.end"
mode="dateTime"
/>
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Resource:"
class="w-full"
>
<InputGroup class="w-full">
<USelectMenu
v-model="resourceToAdd"
:options="dataStore.getResourcesList"
option-attribute="title"
value-attribute="id"
class="w-full"
></USelectMenu>
<UButton
@click="itemInfo.resources.push(dataStore.getResourcesList.find(i => i.id === resourceToAdd))"
:disabled="itemInfo.resources.find(i => i.id === resourceToAdd)"
>
+ Hinzufügen
</UButton>
</InputGroup>
</UFormGroup>
<UTable
v-if="itemInfo.resources.length > 0"
:rows="itemInfo.resources"
:columns="[
{
key:'type',
label: 'Type'
}, {
key: 'title',
label: 'Name'
}, {
key: 'remove'
}
]"
>
<template #remove-data="{row}">
<UButton
color="rose"
variant="outline"
@click="itemInfo.resources = itemInfo.resources.filter(i => i.id !== row.id)"
>
Entfernen
</UButton>
</template>
</UTable>
</UForm>
</template>
<style scoped>
</style>

View File

@@ -1,127 +0,0 @@
<template>
<UDashboardNavbar title="Termine" :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(`/events/edit`)">+ Termin</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(`/events/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Termine anzuzeigen' }"
>
<template #start-data="{row}">
{{dayjs(row.start).format("DD.MM.YY HH:mm")}}
</template>
<template #end-data="{row}">
{{dayjs(row.end).format("DD.MM.YY HH:mm")}}
</template>
<template #project-data="{row}">
{{row.project ? dataStore.getProjectById(row.project).name: ""}}
</template>
<template #resources-data="{row}">
{{row.resources ? row.resources.map(i => i.title).join(", ") : ""}}
</template>
</UTable>
</template>
<script setup>
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/events/[mode]")
}
})
const dataStore = useDataStore()
const router = useRouter()
const templateColumns = [
{
key: 'title',
label: "Titel:",
sortable: true
},
{
key: "start",
label: "Start",
sortable: true
},
{
key: "end",
label: "Ende"
},
{
key: "resources",
label: "Resourcen"
},
{
key: "project",
label: "Projekt"
}
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
const searchString = ref('')
const filteredRows = computed(() => {
if(!searchString.value) {
return dataStore.events
}
return dataStore.events.filter(item => {
return Object.values(item).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
})
</script>
<style scoped>
</style>

550
pages/files/index.vue Normal file
View File

@@ -0,0 +1,550 @@
<script setup>
import {BlobReader, BlobWriter, ZipWriter} from "@zip.js/zip.js";
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
import DocumentUploadModal from "~/components/DocumentUploadModal.vue";
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
/*'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},*/
'+': () => {
//Hochladen
uploadModalOpen.value = true
},
'Enter': {
usingInput: true,
handler: () => {
let entry = renderedFileList.value[selectedFileIndex.value]
if(entry.type === "file") {
showFile(entry.id)
console.log(entry)
} else {
changeFolder(currentFolders.value.find(i => i.id === entry.id))
}
}
},
'arrowdown': () => {
if(selectedFileIndex.value < renderedFileList.value.length - 1) {
selectedFileIndex.value += 1
} else {
selectedFileIndex.value = 0
}
},
'arrowup': () => {
if(selectedFileIndex.value === 0) {
selectedFileIndex.value = renderedFileList.value.length - 1
} else {
selectedFileIndex.value -= 1
}
}
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const router = useRouter()
const route = useRoute()
const slideover = useSlideover()
const modal = useModal()
dataStore.fetchDocuments()
const uploadModalOpen = ref(false)
const createFolderModalOpen = ref(false)
const uploadInProgress = ref(false)
const fileUploadFormData = ref({
tags: ["Eingang"],
path: "",
tenant: profileStore.currentTenant,
folder: null
})
const files = useFiles()
let tags = dataStore.getDocumentTags
const displayMode = ref("list")
const displayModes = ref([{label: 'Liste',key:'list', icon: 'i-heroicons-list-bullet'},{label: 'Kacheln',key:'rectangles', icon: 'i-heroicons-squares-2x2'}])
const documents = ref([])
const folders = ref([])
const filetags = ref([])
const currentFolder = ref(null)
const loadingDocs = ref(false)
const isDragTarget = ref(false)
const setupPage = async () => {
folders.value = await useSupabaseSelect("folders")
documents.value = await files.selectDocuments()
filetags.value = await useSupabaseSelect("filetags")
if(route.query) {
if(route.query.folder) {
currentFolder.value = await useSupabaseSelectSingle("folders", route.query.folder)
}
}
const dropZone = document.getElementById("drop_zone")
dropZone.ondragover = function (event) {
console.log(event)
isDragTarget.value = true
event.preventDefault()
}
dropZone.ondragleave = function (event) {
isDragTarget.value = false
}
dropZone.ondrop = async function (event) {
console.log("files dropped")
event.preventDefault()
await uploadFiles(event.dataTransfer.files)
isDragTarget.value = false
setupPage()
}
loadingDocs.value = false
}
setupPage()
const currentFolders = computed(() => {
if(folders.value.length > 0) {
let tempFolders = folders.value.filter(i => currentFolder.value ? i.parent === currentFolder.value.id : !i.parent)
return tempFolders
} else return []
})
const breadcrumbLinks = computed(() => {
if(currentFolder.value) {
let parents = []
const addParent = (parent) => {
parents.push(parent)
if(parent.parent) {
addParent(folders.value.find(i => i.id === parent.parent))
}
}
if(currentFolder.value.parent) {
addParent(folders.value.find(i => i.id === currentFolder.value.parent))
}
return [{
label: "Home",
click: () => {
changeFolder(null)
},
icon: "i-heroicons-folder"
},
...parents.map(i => {
return {
label: folders.value.find(x => x.id === i.id).name,
click: () => {
changeFolder(i)
},
icon: "i-heroicons-folder"
}
}).reverse(),
{
label: currentFolder.value.name,
click: () => {
changeFolder(currentFolder.value)
},
icon: "i-heroicons-folder"
}]
} else {
return [{
label: "Home",
click: () => {
changeFolder(null)
},
icon: "i-heroicons-folder"
}]
}
})
const filteredDocuments = computed(() => {
return documents.value.filter(i => currentFolder.value ? i.folder === currentFolder.value.id : !i.folder)
})
const changeFolder = async (newFolder) => {
loadingDocs.value = true
currentFolder.value = newFolder
if(newFolder) {
fileUploadFormData.value.folder = newFolder.id
await router.push(`/files?folder=${newFolder.id}`)
} else {
fileUploadFormData.value.folder = null
await router.push(`/files`)
}
setupPage()
}
const createFolderData = ref({})
const createFolder = async () => {
const {data,error} = await supabase
.from("folders")
.insert({
tenant: profileStore.currentTenant,
parent: currentFolder.value ? currentFolder.value.id : undefined,
name: createFolderData.value.name,
})
createFolderModalOpen.value = false
setupPage()
}
const uploadFiles = async (files) => {
uploadInProgress.value = true;
if(files) {
//await dataStore.uploadFiles({tags: ["Ablage"],tenant: profileStore.currentTenant,folder: currentFolder.value.id}, files, true)
await dataStore.uploadFiles({tags: ["Ablage"],tenant: profileStore.currentTenant}, files, true)
} else {
await dataStore.uploadFiles(fileUploadFormData.value, document.getElementById("fileUploadInput").files, true)
}
uploadModalOpen.value = false;
uploadInProgress.value = false;
}
const downloadSelected = async () => {
const bucket = "files";
let files = []
dataStore.documents.filter(doc => doc.selected).forEach(doc => files.push(doc.path))
console.log(files)
// If there are no files in the folder, throw an error
if (!files || !files.length) {
throw new Error("No files to download");
}
const promises = [];
// Download each file in the folder
files.forEach((file) => {
promises.push(
supabase.storage.from(bucket).download(`${file}`)
);
});
// Wait for all the files to download
const response = await Promise.allSettled(promises);
// Map the response to an array of objects containing the file name and blob
const downloadedFiles = response.map((result, index) => {
if (result.status === "fulfilled") {
console.log(files[index].split("/")[files[index].split("/").length -1])
return {
name: files[index].split("/")[files[index].split("/").length -1],
blob: result.value.data,
};
}
});
// Create a new zip file
const zipFileWriter = new BlobWriter("application/zip");
const zipWriter = new ZipWriter(zipFileWriter, { bufferedWrite: true });
// Add each file to the zip file
downloadedFiles.forEach((downloadedFile) => {
if (downloadedFile) {
zipWriter.add(downloadedFile.name, new BlobReader(downloadedFile.blob));
}
});
// Download the zip file
const url = URL.createObjectURL(await zipWriter.close());
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "documents.zip");
document.body.appendChild(link);
link.click();
}
const renderedFileList = computed(() => {
let files = filteredDocuments.value.map(i => {
return {
label: i.path.split("/")[i.path.split("/").length -1],
id: i.id,
type: "file"
}
})
let folders = currentFolders.value.map(i => {
return {
label: i.name,
id: i.id,
type: "folder"
}
})
return [...folders,...files]
})
const selectedFileIndex = ref(0)
const showFile = (fileId) => {
console.log(fileId)
modal.open(DocumentDisplayModal,{
documentData: documents.value.find(i => i.id === fileId),
})
}
</script>
<template>
<UDashboardNavbar
title="Dateien"
>
</UDashboardNavbar>
<UDashboardToolbar>
<template #left>
<UBreadcrumb
:links="breadcrumbLinks"
/>
</template>
<template #right>
<USelectMenu
:options="displayModes"
value-attribute="key"
option-attribute="label"
v-model="displayMode"
:ui-menu="{ width: 'min-w-max'}"
>
<template #label>
<UIcon class="w-5 h-5" :name="displayModes.find(i => i.key === displayMode).icon"/>
</template>
</USelectMenu>
<UButton @click="modal.open(DocumentUploadModal,{fileData: {folder: currentFolder.id}})">+ Hochladen</UButton>
<UButton
@click="createFolderModalOpen = true"
variant="outline"
>+ Ordner</UButton>
<UModal
v-model="createFolderModalOpen"
>
<UCard>
<template #header>
Ordner Erstellen
</template>
<UFormGroup
label="Ordner erstellen"
>
<UInput
v-model="createFolderData.name"
/>
</UFormGroup>
<template #footer>
<UButton
@click="createFolder"
>
Erstellen
</UButton>
</template>
</UCard>
</UModal>
</template>
</UDashboardToolbar>
<div id="drop_zone" class="h-full scrollList">
<UDashboardPanelContent v-if="!isDragTarget" >
<div v-if="displayMode === 'list'">
<table class="w-full">
<thead>
<tr>
<td class="font-bold">Name</td>
<td class="font-bold">Erstellt am</td>
</tr>
</thead>
<tr v-for="(entry,index) in renderedFileList">
<td>
<UIcon class="mr-1" :name="entry.type === 'folder' ? 'i-heroicons-folder' : 'i-heroicons-document'"/>
<a
style="cursor: pointer"
:class="[...index === selectedFileIndex ? ['text-primary', 'text-xl'] : ['dark:text-white','text-black','text-xl']]"
@click="entry.type === 'folder' ? changeFolder(currentFolders.find(i => i.id === entry.id)) : showFile(entry.id)"
>{{entry.label}}</a>
</td>
<td>
<span v-if="entry.type === 'file'" class="text-xl">{{dayjs(documents.find(i => i.id === entry.id).created_at).format("DD.MM.YY HH:mm")}}</span>
<span v-if="entry.type === 'folder'" class="text-xl">{{dayjs(currentFolders.find(i => i.id === entry.id).created_at).format("DD.MM.YY HH:mm")}}</span>
</td>
</tr>
</table>
</div>
<div v-else-if="displayMode === 'rectangles'">
<div class="flex flex-row w-full flex-wrap" v-if="currentFolders.length > 0">
<a
class="w-1/6 folderIcon flex flex-col p-5 m-2"
v-for="folder in currentFolders"
@click="changeFolder(folder)"
>
<UIcon
name="i-heroicons-folder"
class="w-20 h-20"
/>
<span class="text-center truncate">{{folder.name}}</span>
</a>
</div>
<UDivider class="my-5" v-if="currentFolder">{{currentFolder.name}}</UDivider>
<UDivider class="my-5" v-else>Ablage</UDivider>
<div v-if="!loadingDocs">
<DocumentList
v-if="filteredDocuments.length > 0"
:documents="filteredDocuments"
@selectDocument="(info) => console.log(info)"
/>
<UAlert
v-else
class="mt-5 w-1/2 mx-auto"
icon="i-heroicons-light-bulb"
title="Keine Dokumente vorhanden"
color="primary"
variant="outline"
/>
</div>
<UProgress
animation="carousel"
v-else
class="w-2/3 my-5 mx-auto"
/>
</div>
</UDashboardPanelContent>
<UCard
class=" m-5"
v-else>
<template #header>
<p class="mx-auto">Dateien zum hochladen hierher ziehen</p>
</template>
</UCard>
</div>
<USlideover
v-model="uploadModalOpen"
>
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
Datei Hochladen
</template>
<div class="h-full">
<UFormGroup
label="Datei:"
>
<UInput
type="file"
id="fileUploadInput"
multiple
/>
</UFormGroup>
<UFormGroup
label="Tags:"
class="mt-3"
>
<USelectMenu
multiple
searchable
searchable-placeholder="Suchen..."
option-attribute="name"
value-attribute="id"
:options="filetags"
v-model="fileUploadFormData.tags"
/>
</UFormGroup>
</div>
<template #footer>
<UButton
v-if="!uploadInProgress"
class="mt-3"
@click="uploadFiles"
>Hochladen</UButton>
<UProgress
v-else
animation="carousel"
/>
</template>
</UCard>
</USlideover>
</template>
<style scoped>
.folderIcon {
border: 1px solid lightgrey;
border-radius: 10px;
color: dimgrey;
}
.folderIcon:hover {
border: 1px solid #69c350;
color: #69c350;
}
tr:nth-child(odd) {
background-color: rgba(0, 0, 0, 0.05);
}
</style>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
</script>
<template>
<UDashboardNavbar>
<template #center>
<h1
:class="['text-xl','font-medium']"
>Zentrales Logbuch</h1>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<HistoryDisplay/>
</UDashboardPanelContent>
</template>
<style scoped>
</style>

View File

@@ -8,21 +8,22 @@ definePageMeta({
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const {vendors} = storeToRefs(useDataStore())
const {fetchVendorInvoices} = useDataStore()
const availableDocuments = 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)).data.map(i => i.id)
availableDocuments.value = await useFiles().selectSomeDocuments(ids)
const availableDocuments = computed(() => {
}
//console.log(dataStore.documents.filter(i => i.tags.includes('Eingangsrechnung') && !dataStore.incominginvoices.find(x => x.document === i.id)))
//console.log(dataStore.documents.filter(i => i.tags.includes('Eingangsrechnung')).length)
return dataStore.documents.filter(i => i.tags.includes('Eingangsrechnung') && !i.tags.includes("Archiviert") && !dataStore.incominginvoices.find(x => x.document === i.id))
})
setup()
//let currentDocument = ref(null)
@@ -56,7 +57,7 @@ const itemInfo = ref({
//Functions
const setupPage = async () => {
if((mode.value === "show" || mode.value === "edit" ) && route.params.id){
itemInfo.value = await dataStore.getIncomingInvoiceById(Number(route.params.id))
itemInfo.value = await useSupabaseSelectSingle("incominginvoices",route.params.id)
//currentDocument.value = await dataStore.getDocumentById(currentVendorInvoice.value.document)
}
console.log(itemInfo.value)

View File

@@ -0,0 +1,479 @@
<script setup>
import InputGroup from "~/components/InputGroup.vue";
import dayjs from "dayjs";
import HistoryDisplay from "~/components/HistoryDisplay.vue";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const itemInfo = ref({
vendor: 0,
expense: true,
reference: "",
date: null,
dueDate: null,
paymentType: "Überweisung",
description: "",
state: "Entwurf",
accounts: [
{
account: null,
amountNet: null,
amountTax: null,
taxType: "19",
costCentre: null
}
]
})
const availableDocuments = ref([])
const 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)
}
setup()
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)
console.log(data)
const {error} = await supabase.from("files").update({incominginvoice: data[0].id}).eq("id",loadedFile.value.id)
}
</script>
<template>
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
<template #right>
<UButton
@click="createIncomingInvoice"
>
Speichern
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<div v-if="!loadedFile">
<DocumentList
:documents="availableDocuments"
:return-document-id="true"
@selectDocument="(documentId) => loadFile(documentId)"
/>
</div>
<div
v-else
class="flex justify-between mt-5"
>
<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 w-full"
/>
<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">
<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:" required>
<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..."
>
<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>
<UButton
@click="router.push('/vendors/create')"
>
+ Lieferant
</UButton>
</InputGroup>
</UFormGroup>
<UFormGroup
class="mt-3"
label="Rechnungsreferenz:"
required
>
<UInput
v-model="itemInfo.reference"
/>
</UFormGroup>
<InputGroup class="mt-3" gap="2">
<UFormGroup label="Rechnungsdatum:" required>
<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'" />
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.date" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup label="Fälligkeitsdatum:" required>
<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'" />
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
</InputGroup>
<UFormGroup label="Zahlart:" required>
<USelectMenu
:options="['Einzug','Kreditkarte','Überweisung','Sonstiges']"
v-model="itemInfo.paymentType"
/>
</UFormGroup>
<UFormGroup label="Beschreibung:" required>
<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="dataStore.accounts"
option-attribute="label"
value-attribute="id"
searchable
:search-attributes="['label']"
searchable-placeholder="Suche..."
v-model="item.account"
>
<template #label>
{{dataStore.accounts.find(account => account.id === item.account) ? dataStore.accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Kostenstelle"
class=" mb-3"
>
<USelectMenu
:options="dataStore.getCostCentresComposed"
option-attribute="label"
value-attribute="id"
searchable
:search-attributes="['label']"
searchable-placeholder="Suche..."
v-model="item.costCentre"
>
<template #label>
{{dataStore.getCostCentresComposed.find(account => account.id === item.costCentre) ? dataStore.getCostCentresComposed.find(account => account.id === item.costCentre).label : "Keine Kostenstelle ausgewählt" }}
</template>
</USelectMenu>
</UFormGroup>
<InputGroup>
<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"
option-attribute="label"
@change="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>
<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"
:disabled="item.taxType === null"
@keyup="item.amountTax = Number((item.amountNet * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2))"
>
<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"
@keyup="item.amountNet = Number((item.amountGross / (1 + Number(item.taxType)/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>
</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)]"
>
Position hinzufügen
</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;
}
.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;
}
</style>

View File

@@ -133,7 +133,7 @@ const isPaid = (item) => {
</template>
</UInput>
<UButton @click="router.push(`/incomingInvoices/edit`)">+ Beleg</UButton>
<UButton @click="router.push(`/incomingInvoices/create`)">+ Beleg</UButton>
</template>
</UDashboardNavbar>
<UDashboardToolbar>

View File

@@ -15,48 +15,28 @@
<UDashboardPanelContent>
<UDashboardCard
class="mt-3"
title="Anwesenheiten"
v-if="dataStore.getStartedWorkingTimes().length > 0"
>
<p v-for="time in dataStore.getStartedWorkingTimes()"><UIcon name="i-heroicons-check"/>{{dataStore.getProfileById(time.profile).fullName}}</p>
<display-present-profiles/>
</UDashboardCard>
<!--TODO: Fix Card Table overflowing <UDashboardCard
title="Offene Aufgaben"
v-if="dataStore.getOpenTasksCount > 0"
class="w-1/2 h-1/2"
>
<UTable
:rows="dataStore.tasks.filter(i => i.categorie !== 'Erledigt' && (i.profile === dataStore.activeProfile.id ||!i.profile))"
@select="(row) => router.push(`/tasks/show/${row.id}`)"
:columns="[
{
key: 'categorie',
label: 'Kategorie'
},{
key: 'name',
label: 'Name'
},
]"
>
<template #categorie-data="{row}">
<span v-if="row.categorie === 'Dringend'" class="text-rose-500">{{row.categorie}}</span>
<span v-else-if="row.categorie === 'In Bearbeitung'" class="text-primary-500">{{row.categorie}}</span>
<span v-else>{{row.categorie}}</span>
</template>
</UTable>
</UDashboardCard>-->
<UDashboardCard
:ui="{ body: { padding: '!pb-3 !px-5' }}"
class="mt-3"
>
<display-income-and-expenditure/>
</UDashboardCard>
<UDashboardCard
class="w-1/3 mt-3"
:ui="{ body: { padding: '!py-5 !px-5' }}"
>
<display-open-balances/>
</UDashboardCard>
<UDashboardCard
class="w-1/3 mt-3"
>
<display-running-time/>
</UDashboardCard>
</UDashboardPanelContent>
</UDashboardPanel>
</UDashboardPage>
@@ -64,11 +44,14 @@
<script setup>
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const toast = useToast()
const router = useRouter()

View File

@@ -4,6 +4,7 @@ definePageMeta({
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const router = useRouter()
const mode = ref("incoming")
@@ -41,8 +42,8 @@ const createMovement = async () => {
spaceId: inventoryChangeData.value.destinationSpaceId,
projectId: inventoryChangeData.value.destinationProjectId,
quantity: inventoryChangeData.value.quantity,
profileId: dataStore.activeProfile.id,
tenant: dataStore.currentTenant
profileId: profileStore.activeProfile.id,
tenant: profileStore.currentTenant
}
movements.push(movement)
@@ -61,8 +62,8 @@ const createMovement = async () => {
spaceId: inventoryChangeData.value.sourceSpaceId,
projectId: inventoryChangeData.value.sourceProjectId,
quantity: inventoryChangeData.value.quantity * -1,
profileId: dataStore.activeProfile.id,
tenant: dataStore.currentTenant
profileId: profileStore.activeProfile.id,
tenant: profileStore.currentTenant
}
movements.push(movement)
@@ -72,16 +73,16 @@ const createMovement = async () => {
spaceId: inventoryChangeData.value.sourceSpaceId,
projectId: inventoryChangeData.value.sourceProjectId,
quantity: inventoryChangeData.value.quantity * -1,
profileId: dataStore.activeProfile.id,
tenant: dataStore.currentTenant
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: dataStore.activeProfile.id,
tenant: dataStore.currentTenant
profileId: profileStore.activeProfile.id,
tenant: profileStore.currentTenant
}
movements.push(outMovement)

View File

@@ -1,382 +0,0 @@
<script setup>
import HistoryDisplay from "~/components/HistoryDisplay.vue";
import dayjs from "dayjs";
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
import DocumentUpload from "~/components/DocumentUpload.vue";
import DocumentList from "~/components/DocumentList.vue";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
name: null,
description: null,
quantity: 0
})
//Functions
const setupPage = async () => {
if(mode.value === "show"){
itemInfo.value = await useSupabaseSelectSingle("inventoryitems", route.params.id, "*, vendor(*), checks(*)")
} else if(mode.value === "edit") {
itemInfo.value = await useSupabaseSelectSingle("inventoryitems", route.params.id, "*")
}
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/inventoryitems/show/${itemInfo.value.id}`)
} else {
router.push(`/inventoryitems`)
}
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Inventartikel erstellen' : 'Inventartikel bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/inventoryitems`)"
>
Inventar
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium']"
>{{itemInfo.id ? `Inventarartikel: ${itemInfo.name}` : (mode === 'create' ? 'Inventarartikel erstellen' : 'Inventarartikel bearbeiten')}}</h1>
</template>
<template #right>
<ButtonWithConfirm
color="rose"
variant="outline"
@confirmed="dataStore.updateItem('inventoryitems',{...itemInfo, archived: true})"
v-if="mode === 'edit'"
>
<template #button>
Archivieren
</template>
<template #header>
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
</template>
Möchten Sie den Inventarartikel {{itemInfo.name}} wirklich archivieren?
</ButtonWithConfirm>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('inventoryitems',itemInfo)"
>
Speichern
</UButton>
<UButton
v-if="mode === 'create'"
@click="dataStore.createNewItem('inventoryitems',itemInfo)"
>
Erstellen
</UButton>
<UButton
v-if="mode === 'create'"
class="ml-2"
@click="dataStore.createNewItem('inventoryitems',itemInfo);
itemInfo = {
name: null,
description: null,
quantity: 0
}"
>
Erstellen + Neu
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click=" router.push(`/inventoryitems/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="[{label: 'Informationen'},{label: 'Dokumente'},{label: 'Überprüfungen'}]"
v-if="itemInfo && mode === 'show'"
class="p-5"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="flex-row flex mt-5">
<div class="w-1/2 mr-5">
<UCard>
<UAlert
v-if="itemInfo.archived"
color="rose"
variant="outline"
title="Objekt archiviert"
icon="i-heroicons-light-bulb"
class="mb-5"
/>
<table class="w-full">
<tr>
<td>Name: </td>
<td>{{itemInfo.name}}</td>
</tr>
<tr v-if="itemInfo.currentSpace">
<td>Lagerplatz: </td>
<td>{{dataStore.getSpaceById(itemInfo.currentSpace).spaceNumber}} - {{dataStore.getSpaceById(itemInfo.currentSpace).description}}</td>
</tr>
<tr>
<td>Seriennummer:</td>
<td>{{itemInfo.serialNumber}}</td>
</tr>
<tr>
<td>Menge:</td>
<td>{{itemInfo.quantity > 0 ? itemInfo.quantity : 'Einzelarktikel'}}</td>
</tr>
<tr>
<td>Artikelnummer:</td>
<td>{{itemInfo.articleNumber}}</td>
</tr>
<tr>
<td>Hersteller:</td>
<td>{{itemInfo.manufacturer}}</td>
</tr>
<tr>
<td>Herstellernummer:</td>
<td>{{itemInfo.manufacturerNumber}}</td>
</tr>
<tr>
<td>Lieferant:</td>
<td>{{itemInfo.vendor ? itemInfo.vendor.name : ''}}</td>
</tr>
<tr>
<td>Kaufdatum:</td>
<td>{{itemInfo.purchaseDate ? dayjs(itemInfo.purchaseDate).format("DD.MM.YYYY") : ''}}</td>
</tr>
<tr>
<td>Wert bei Kauf:</td>
<td>{{itemInfo.purchasePrice ? itemInfo.purchasePrice.toFixed(2).replace(".",",") + " €" : ''}}</td>
</tr>
<tr>
<td>Aktueller Wert:</td>
<td>{{itemInfo.currentValue ? itemInfo.currentValue.toFixed(2).replace(".",",") + " €" : ''}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.description}}</td>
</tr>
</table>
</UCard>
</div>
<div class="w-1/2">
<UCard>
<HistoryDisplay
type="inventoryitem"
v-if="itemInfo"
render-headline
:element-id="itemInfo.id"
/>
</UCard>
</div>
</div>
<div v-else-if="item.label === 'Dokumente'">
<UCard>
<DocumentUpload
type="inventoryitem"
:element-id="itemInfo.id"
/>
<DocumentList :documents="dataStore.getDocumentsByProfileId(itemInfo.id)"/>
</UCard>
</div>
<div v-else-if="item.label === 'Überprüfungen'">
<UCard>
<UTable
:rows="itemInfo.checks"
:columns="[{key:'name',label: 'Name'},{key:'rhythm',label: 'Rhythmus'},{key:'description',label: 'Beschreibung'}]"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/checks/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Überprüfungen anzuzeigen' }"
>
<template #rhythm-data="{row}">
{{row.distance}}
<span v-if="row.distanceUnit === 'dayjs'">Tage</span>
<span v-if="row.distanceUnit === 'years'">Jahre</span>
</template>
</UTable>
</UCard>
</div>
</template>
</UTabs>
<UForm
v-else-if="mode == 'edit' || mode == 'create'"
class="p-5"
>
<div class="flex flex-row">
<div class="w-1/2 mr-5">
<UDivider>
Allgemeines
</UDivider>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Artikelnummer:"
>
<UInput
v-model="itemInfo.articleNumber"
placeholder="Leer lassen für automatische generierte Nummer"
/>
</UFormGroup>
<UFormGroup
label="Lagerplatz:"
>
<USelectMenu
:options="dataStore.spaces"
v-model="itemInfo.currentSpace"
value-attribute="id"
>
<template #option="{option}">
<span class="truncate">{{option.spaceNumber}} - {{option.description}}</span>
</template>
<template #label>
<span v-if="itemInfo.currentSpace">{{dataStore.getSpaceById(itemInfo.currentSpace).spaceNumber }} - {{dataStore.getSpaceById(itemInfo.currentSpace).description}}</span>
<span v-else>Kein Lagerplatz ausgewählt</span>
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Seriennummer:"
>
<UInput
v-model="itemInfo.serialNumber"
/>
</UFormGroup>
<UFormGroup
label="Menge:"
:help="itemInfo.serialNumber ? 'Menge deaktiviert durch Eingabe der Seriennummer' : 'Für Einzelartikel Menge gleich 0'"
>
<UInput
type="number"
v-model="itemInfo.quantity"
:disabled="itemInfo.serialNumber"
/>
</UFormGroup>
</div>
<div class="w-1/2">
<UDivider>
Anschaffung
</UDivider>
<UFormGroup
label="Hersteller:"
>
<UInput
v-model="itemInfo.manufacturer"
/>
</UFormGroup>
<UFormGroup
label="Herstellernr.:"
>
<UInput
v-model="itemInfo.manufacturerNumber"
/>
</UFormGroup>
<UFormGroup
label="Lieferant:"
>
<USelectMenu
:options="dataStore.vendors"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
v-model="itemInfo.vendor"
/>
</UFormGroup>
<UFormGroup
label="Kaufdatum:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.purchaseDate ? dayjs(itemInfo.purchaseDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.purchaseDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Wert bei Kauf:"
>
<UInput
v-model="itemInfo.purchasePrice"
type="number"
steps="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</UFormGroup>
<UFormGroup
label="Aktueller Wert:"
>
<UInput
v-model="itemInfo.currentValue"
type="number"
steps="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</UFormGroup>
</div>
</div>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,117 +0,0 @@
<template>
<UDashboardNavbar title="Inventar" :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(`/inventoryitems/create`)">+ Inventarartikel</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(`/inventoryitems/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Inventarartikel anzuzeigen' }"
>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/tasks/create")
}
})
const dataStore = useDataStore()
const router = useRouter()
const items = ref([])
const setupPage = async () => {
items.value = await useSupabaseSelect("inventoryitems","*", "articleNumber")
}
setupPage()
const templateColumns = [
{
key: "articleNumber",
label: "Artikelnummer",
sortable: true
},{
key: "name",
label: "Name",
sortable: true
},
{
key: "description",
label: "Beschreibung",
sortable: true
}
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
const searchString = ref('')
const filteredRows = computed(() => {
if(!searchString.value) {
return items.value
}
return items.value.filter(product => {
return Object.values(product).some((value) => {
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
})
})
})
</script>
<style scoped>
</style>

View File

@@ -1,5 +1,7 @@
<script setup >
import {useProfileStore} from "~/stores/profile.js";
definePageMeta({
layout: "notLoggedIn"
})
@@ -8,8 +10,8 @@ const supabase = useSupabaseClient()
const user = useSupabaseUser()
const router = useRouter()
const colorMode = useColorMode()
const dataStore = useDataStore()
const toast = useToast()
const profileStore = useProfileStore()
const isLight = computed({
get () {
@@ -61,7 +63,7 @@ const onSubmit = async (data) => {
} else {
//console.log("Login Successful")
dataStore.initializeData(user.id)
profileStore.initializeData(user.id)
router.push("/")

View File

@@ -1,255 +0,0 @@
<script setup>
import HistoryDisplay from "~/components/HistoryDisplay.vue";
import DocumentList from "~/components/DocumentList.vue";
import DocumentUpload from "~/components/DocumentUpload.vue";
import Toolbar from "~/components/Toolbar.vue";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
description: {
html: ""
}
})
const tabItems = [
{
label: "Informationen"
},{
label: "Projekte"
},{
label: "Aufgaben"
},{
label: "Dokumente"
}
]
//Functions
const setupPage = async () => {
if(mode.value === "show"){
itemInfo.value = (await supabase.from("plants").select("*, customer(*)").eq("id",useRoute().params.id).single()).data
} else if(mode.value === "edit") {
itemInfo.value = (await supabase.from("plants").select().eq("id",useRoute().params.id).single()).data
}
if(mode.value === "create") {
let query = route.query
if(query.customer) itemInfo.value.customer = Number(query.customer)
}
}
const contentChanged = (content) => {
itemInfo.value.description.html = content.html
itemInfo.value.description.text = content.text
itemInfo.value.description.json = content.json
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Objekt erstellen' : 'Objekt bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/plants`)"
>
Objekte
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium']"
>{{itemInfo ? `Objekt: ${itemInfo.name}` : (mode === 'create' ? 'Objekt erstellen' : 'Objekt bearbeiten')}}</h1>
</template>
<template #right>
<ButtonWithConfirm
color="rose"
variant="outline"
@confirmed="dataStore.updateItem('plants',{...itemInfo, archived: true})"
v-if="mode === 'edit'"
>
<template #button>
Archivieren
</template>
<template #header>
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
</template>
Möchten Sie das Objekt {{itemInfo.name}} wirklich archivieren?
</ButtonWithConfirm>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('plants',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('plants',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="router.push(itemInfo.id ? `/plants/show/${itemInfo.value.id}` : `/plants/`)"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/plants/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="tabItems"
v-if="mode === 'show'"
class="p-5"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="flex flex-row mt-5">
<UCard class="w-1/2 mr-5">
<UAlert
v-if="itemInfo.archived"
color="rose"
variant="outline"
title="Objekt archiviert"
icon="i-heroicons-light-bulb"
class="mb-5"
/>
<div class="text-wrap">
<table>
<tr>
<td>Kunde:</td>
<td><nuxt-link v-if="itemInfo.customer" :to="`/customers/show/${itemInfo.customer.id}`">{{itemInfo.customer.name}}</nuxt-link></td>
</tr>
</table>
</div>
<div v-if="itemInfo.description.html">
<p>Notizen:</p>
<span v-html="itemInfo.description.html"></span>
</div>
</UCard>
<UCard class="w-1/2">
<HistoryDisplay
type="plant"
v-if="itemInfo.id"
:element-id="itemInfo.id"
render-headline
/>
</UCard>
</div>
<div v-else-if="item.label === 'Projekte'">
<Toolbar>
<UButton
@click="router.push(`/projects/create?plant=${itemInfo.id}`)"
>
+ Projekt
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getProjectsByPlantId(itemInfo.id)"
:columns="[{key: 'name', label: 'Name'}]"
@select="(row) => router.push(`/projects/show/${row.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Projekte' }"
>
</UTable>
</div>
<div v-else-if="item.label === 'Aufgaben'">
<Toolbar>
<UButton
@click="router.push(`/tasks/create?plant=${itemInfo.id}`)"
>
+ Aufgabe
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getTasksByPlantId(itemInfo.id)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Aufgaben' }"
:columns="[{key: 'name', label: 'Name'},{key: 'categore', label: 'Kategorie'}]"
@select="(row) => router.push(`/tasks/show/${row.id}`)"
>
</UTable>
</div>
<div v-else-if="item.label === 'Dokumente'" class="space-y-3">
<Toolbar>
<DocumentUpload
type="plant"
:element-id="itemInfo.id"
/>
</Toolbar>
<DocumentList :documents="dataStore.getDocumentsByPlantId(itemInfo.id)"/>
</div>
</template>
</UTabs>
<UForm
v-else-if="mode === 'edit' || mode === 'create'"
class="p-5"
>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Kundennummer:"
>
<USelectMenu
v-model="itemInfo.customer"
:options="dataStore.customers"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.customers.find(customer => customer.id === itemInfo.customer) ? dataStore.customers.find(customer => customer.id === itemInfo.customer).name : "Kunde auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
<Tiptap
v-if="itemInfo.description"
@updateContent="contentChanged"
:preloadedContent="itemInfo.description.html"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
</style>

View File

@@ -1,139 +0,0 @@
<template>
<UDashboardNavbar title="Objekte" :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(`/plants/create`)">+ Objekt</UButton>
</template>
</UDashboardNavbar>
<UDashboardToolbar>
<template #left>
<UCheckbox
label="Erledigte Anzeigen"
v-model="showDone"
/>
</template>
<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(`/plants/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Objekte anzuzeigen' }"
>
<template #customer-data="{row}">
{{row.customer ? row.customer.name : ""}}
</template>
</UTable>
</template>
<script setup>
import {useListFilter} from "~/composables/useSearch.js";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/plants/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/plants/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("plants","*, customer(id,name)")
}
setupPage()
const templateColumns = [
{
key: "name",
label: "Name",
sortable: true
},{
key: "customer",
label: "Kunde",
sortable: true
}
]
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)
})
</script>
<style scoped>
</style>

View File

@@ -1,179 +0,0 @@
<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";
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/productcategories")
},
'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 )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({})
const openTab = ref(0)
//Functions
const setupPage = async () => {
if(mode.value === "show" || mode.value === "edit"){
itemInfo.value = await useSupabaseSelectSingle("productcategories",route.params.id,"*")
}
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/productcategories/show/${itemInfo.value.id}`)
} else {
router.push(`/productcategories/`)
}
}
setupPage()
</script>
<template>
<UDashboardNavbar
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/productcategories`)"
>
Artikelkategorien
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
class="text-xl font-medium"
>{{itemInfo.name ? `Artikelkategorie: ${itemInfo.name}` : (mode === 'create' ? 'Artikelkategorie erstellen' : 'Artikelkategorie bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('productcategories',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('productcategories',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click=" router.push(`/productcategories/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="[{label: 'Informationen'}]"
v-if="mode === 'show' && itemInfo"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="mr-5 flex flex-row">
<div class="w-1/2 mr-5">
<UCard>
<table class="w-full">
<tr>
<td>Name: </td>
<td>{{itemInfo.name}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.description}}</td>
</tr>
</table>
</UCard>
</div>
<div class="w-1/2">
<UCard>
<HistoryDisplay
type="product"
v-if="itemInfo"
:element-id="itemInfo.id"
render-headline
/>
</UCard>
</div>
</div>
</template>
</UTabs>
<UForm
v-else-if="mode == 'edit' || mode == 'create'"
class="p-5"
>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
autofocus
/>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,139 +0,0 @@
<template>
<UDashboardNavbar title="Artikelkategorien" :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(`/productcategories/create`)">+ Artikelkategorie</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(`/productcategories/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikelkategorien anzuzeigen' }"
>
<template #name-data="{row}">
<span
v-if="row === filteredRows[selectedItem]"
class="text-primary-500 font-bold">{{row.name}}</span>
<span v-else>
{{row.name}}
</span>
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/productscategories/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/productscategories/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("productcategories","*")
}
setupPage()
const templateColumns = [
{
key: "name",
label: "Name",
sortable: true
},
{
key: "description",
label: "Beschreibung",
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>

View File

@@ -1,353 +0,0 @@
<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";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/products")
},
'arrowleft': () => {
if(openTab.value > 0){
openTab.value -= 1
}
},
'arrowright': () => {
if(openTab.value < 3) {
openTab.value += 1
}
},
})
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
unit: 1,
tags: [],
productcategories: []
})
const openTab = ref(0)
const productcategories = ref([])
const units = ref([])
//Functions
const setupPage = async () => {
if(mode.value === "show" || mode.value === "edit"){
itemInfo.value = await useSupabaseSelectSingle("products",route.params.id,"*")
}
productcategories.value = await useSupabaseSelect("productcategories","*")
units.value = (await supabase.from("units").select()).data
console.log(units.value[0])
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/products/show/${itemInfo.value.id}`)
} else {
router.push(`/products/`)
}
}
const printLabel = async (content) => {
const label = await useSupabaseSelectSingle("printLabels",1,'*')
usePrintLabel('0dbe30f3-3008-4cde-8a7c-e785b1c22bfc','ZD411',useGenerateZPL(label.handlebarsZPL,{barcode:itemInfo.value.articleNumber }))
}
setupPage()
</script>
<template>
<UDashboardNavbar
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/products`)"
>
Artikel
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
class="text-xl font-medium"
>{{itemInfo.name ? `Artikel: ${itemInfo.name}` : (mode === 'create' ? 'Artikel erstellen' : 'Artikel bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('products',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('products',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click=" router.push(`/products/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="[{label: 'Informationen'},{label: 'Bestand'},{label: 'Dokumente'}]"
v-if="mode === 'show' && itemInfo"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="mt-5 flex flex-row">
<div class="w-1/2 mr-5">
<UCard>
<UBadge
v-for="tag in itemInfo.tags"
class="mr-2"
>
{{tag}}
</UBadge>
<UDivider
class="my-2"
/>
<!--
<UButton @click="printLabel(itemInfo.ean)">Print Label</UButton>
-->
<table class="w-full">
<tr>
<td>Name:</td>
<td>{{itemInfo.name}}</td>
</tr>
<tr>
<td>Hersteller:</td>
<td>{{itemInfo.manufacturer}}</td>
</tr>
<tr>
<td>Herstellernummer:</td>
<td>{{itemInfo.manufacturerNumber}}</td>
</tr>
<tr>
<td>Einheit:</td>
<td>{{units.find(i => i.id === itemInfo.unit) ? units.find(i => i.id === itemInfo.unit).name : ""}}</td>
</tr>
<tr>
<td>Tags:</td>
<td>{{itemInfo.tags.join(", ")}}</td>
</tr>
<tr>
<td>Artikelkategorien:</td>
<td>{{itemInfo.productcategories.map(i => productcategories.find(x => x.id === i).name).join(", ")}}</td>
</tr>
<tr>
<td>EAN:</td>
<td>{{itemInfo.ean}}</td>
</tr>
<tr>
<td>Verkaufspreis:</td>
<td>{{itemInfo.sellingPrice}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.description}}</td>
</tr>
</table>
</UCard>
</div>
<div class="w-1/2">
<UCard>
<HistoryDisplay
type="product"
v-if="itemInfo"
:element-id="itemInfo.id"
render-headline
/>
</UCard>
</div>
</div>
<div
v-if="item.label === 'Bestand'"
>
<UCard class="mt-5">
Bestand: {{dataStore.getStockByProductId(itemInfo.id)}} {{dataStore.units.find(unit => unit.id === itemInfo.unit) ? dataStore.units.find(unit => unit.id === itemInfo.unit).name : ""}}
</UCard>
</div>
<div
v-if="item.label === 'Dokumente'"
>
<UCard class="mt-5">
<Toolbar>
<DocumentUpload
type="product"
:element-id="itemInfo.id"
/>
</Toolbar>
<DocumentList :documents="dataStore.getDocumentsByProductId(itemInfo.id)"/>
</UCard>
</div>
</template>
</UTabs>
<UForm
v-else-if="mode == 'edit' || mode == 'create'"
class="p-5"
>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
autofocus
/>
</UFormGroup>
<UFormGroup
label="Hersteller:"
>
<UInput
v-model="itemInfo.manufacturer"
/>
</UFormGroup>
<UFormGroup
label="Hersteller Nr.:"
>
<UInput
v-model="itemInfo.manufacturerNumber"
/>
</UFormGroup>
<UFormGroup
label="Einheit:"
>
<USelectMenu
v-model="itemInfo.unit"
:options="dataStore.units"
option-attribute="name"
value-attribute="id"
>
<template #label>
{{dataStore.units.find(unit => unit.id === itemInfo.unit) ? dataStore.units.find(unit => unit.id === itemInfo.unit).name : itemInfo.unit }}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Tags:"
>
<USelectMenu
v-model="itemInfo.tags"
:options="dataStore.ownTenant.tags.products"
multiple
>
<template #label>
{{itemInfo.tags.length > 0 ? itemInfo.tags.join(", ") : "Keine Tags ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Artikelkategorien:"
>
<USelectMenu
v-model="itemInfo.productcategories"
:options="productcategories"
value-attribute="id"
option-attribute="name"
multiple
>
<template #label>
{{itemInfo.productcategories.length > 0 ? itemInfo.productcategories.map(i => productcategories.find(x => x.id === i).name).join(", ") : "Keine Kategorien ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="EAN:"
>
<UInput
v-model="itemInfo.ean"
/>
</UFormGroup>
<!-- <UFormGroup
label="Barcode:"
>
<UInput
v-model="itemInfo.barcode"
/>
</UFormGroup>-->
<UFormGroup
label="Einkaufspreis:"
>
<UInput
v-model="itemInfo.purchasePrice"
type="number"
steps="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</UFormGroup>
<UFormGroup
label="Verkaufspreis:"
>
<UInput
v-model="itemInfo.sellingPrice"
type="number"
steps="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,220 +0,0 @@
<template>
<UDashboardNavbar title="Artikel" :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 #left>
<USelectMenu
v-model="selectedTags"
icon="i-heroicons-adjustments-horizontal-solid"
:options="templateTags"
multiple
class="hidden lg:block"
>
<template #label>
Tags
</template>
</USelectMenu>
</template>
<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>
<template #sellingPrice-data="{row}">
{{row.sellingPrice ? (Number(row.sellingPrice).toFixed(2) + " €").replace(".",",") : ""}}
</template>
<template #tags-data="{row}">
<UBadge
v-if="row.tags.length > 0"
v-for="tag in row.tags"
class="mr-2"
>
{{tag}}
</UBadge>
<span v-else>-</span>
</template>
<template #unit-data="{row}">
{{dataStore.units.find(unit => unit.id === row.unit) ? dataStore.units.find(unit => unit.id === row.unit).name : row.unit}}
</template>
<template #productcategories-data="{row}">
{{row.productcategories.map(i => productcategories.find(x => x.id === i).name).join(", ")}}
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
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 productcategories = ref([])
const selectedItem = ref(0)
const setupPage = async () => {
items.value = await useSupabaseSelect("products","*")
productcategories.value = await useSupabaseSelect("productcategories", "*")
}
setupPage()
const templateColumns = [
{
key: "stock",
label: "Bestand"
},
{
key: "name",
label: "Name",
sortable: true
},
{
key: "description",
label: "Beschreibung",
sortable: true
},
{
key: "manufacturer",
label: "Hersteller",
sortable: true
},
{
key: "unit",
label: "Einheit",
sortable: true
},
{
key: "sellingPrice",
label: "Verkaufpreispreis",
sortable: true
},
{
key: "tags",
label: "Tags",
sortable: true
},
{
key: "productcategories",
label: "Artikelkategorien",
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>

View File

@@ -1,21 +1,19 @@
<script setup>
const dataStore = useDataStore()
const profileStore = useProfileStore()
const router = useRouter()
const templateColumns = [
{
key: 'employeeNumber',
label: "MA-Nummer:",
sortable: true
label: "MA-Nummer",
},{
key: 'fullName',
label: "Name:",
sortable: true
label: "Name",
},{
key: "email",
label: "E-Mail:",
sortable: true
label: "E-Mail",
}
]
const selectedColumns = ref(templateColumns)
@@ -35,7 +33,7 @@
</UDashboardNavbar>
<UTable
:rows="dataStore.profiles"
:rows="profileStore.profiles"
@select="(item) => router.push(`/profiles/show/${item.id}`)"
:columns="columns"
>

View File

@@ -12,6 +12,7 @@ dayjs.extend(isoWeek)
dayjs.extend(isBetween)
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const supabase = useSupabaseClient()
@@ -33,11 +34,10 @@ const setupPage = async () => {
setupPage()
const emailSignature = ref(dataStore.activeProfile.emailSignature)
const emailSignature = ref(profileStore.activeProfile.emailSignature)
const tiptapLoaded = ref(false)
const contentSaved = ref(true)
const contentChanged = async (content) => {
console.log(tiptapLoaded)
emailSignature.value = content.html
if(tiptapLoaded.value === true) {
contentSaved.value = false
@@ -49,7 +49,7 @@ const contentChanged = async (content) => {
const saveSignature = async () => {
const {data,error} = await supabase.from("profiles").update({emailSignature: emailSignature.value}).eq("id", dataStore.activeProfile.id)
const {data,error} = await supabase.from("profiles").update({emailSignature: emailSignature.value}).eq("id", profileStore.activeProfile.id)
if(error) {
toast.add({title: "Fehler beim Speichern der Signatur", color:"rose"})
@@ -66,14 +66,13 @@ const addToNewsletter = async () => {
url: "https://newsletter.fedeo.io/api/public/subscription",
method: "post",
data: {
email: dataStore.activeProfile.email,
name: dataStore.activeProfile.name,
email: profileStore.activeProfile.email,
name: profileStore.activeProfile.name,
list_uuids: ["b97453fd-14b2-4b25-8f9b-b83847317ea3"],
}
})
console.log(data)
}
const calcWeeklyWorkingHours = () => {
@@ -96,6 +95,15 @@ const isLight = computed({
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const saveProfile = async () => {
let data = {...itemInfo.value, fullName: itemInfo.value.firstName + ' ' + itemInfo.value.lastName}
delete data.checks
delete data.documents
await dataStore.updateItem('profiles',data,oldItemInfo.value)
}
</script>
<template>
@@ -143,7 +151,7 @@ const isLight = computed({
<div v-if="item.label === 'Informationen'">
<Toolbar>
<UButton
@click="dataStore.updateItem('profiles',{...itemInfo, fullName: itemInfo.firstName + ' ' + itemInfo.lastName},oldItemInfo)"
@click="saveProfile"
>
Speichern
</UButton>
@@ -200,6 +208,39 @@ const isLight = computed({
/>
</UFormGroup>
</InputGroup>
<InputGroup class="w-full">
<UFormGroup
label="Festnetz Telefon"
class="w-60"
>
<UInput
v-model="itemInfo.fixedTel"
/>
</UFormGroup>
<UFormGroup
label="Handy"
class="flex-auto"
>
<UInput
v-model="itemInfo.mobileTel"
/>
</UFormGroup>
<UFormGroup
label="Geburtstag:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.birthday ? dayjs(itemInfo.birthday).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.birthday" />
</template>
</UPopover>
</UFormGroup>
</InputGroup>
<InputGroup class="w-full">
<UFormGroup
label="Kleidergröße Oberteil"
@@ -227,12 +268,12 @@ const isLight = computed({
</UFormGroup>
</InputGroup>
<UDivider class="my-5" v-if="dataStore.activeProfile.id === itemInfo.id">
<UDivider class="my-5" v-if="profileStore.activeProfile.id === itemInfo.id">
Helligkeitseinstellung
</UDivider>
<UButton
v-if="dataStore.activeProfile.id === itemInfo.id"
v-if="profileStore.activeProfile.id === itemInfo.id"
:icon="isLight ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
color="white"
variant="outline"
@@ -279,11 +320,28 @@ const isLight = computed({
<div v-if="item.label === 'Vertragsdaten'">
<Toolbar>
<UButton
@click="dataStore.updateItem('profiles',itemInfo)"
@click="saveProfile"
>
Speichern
</UButton>
</Toolbar>
<InputGroup class="w-full">
<UFormGroup
label="Eintrittsdatum:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.entryDate ? dayjs(itemInfo.entryDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.entryDate" />
</template>
</UPopover>
</UFormGroup>
</InputGroup>
<InputGroup class="w-full">
<UFormGroup
label="Wöchentliche Arbeitszeit"

View File

@@ -1,675 +0,0 @@
<script setup>
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/projects")
},
'arrowleft': () => {
if(openTab.value > 0){
openTab.value -= 1
}
},
'arrowright': () => {
if(openTab.value < 3) {
openTab.value += 1
}
},
})
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const user = useSupabaseUser()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
const openTab = ref(0)
const tabItems = [
{
key: "information",
label: "Informationen"
},
{
key: "phases",
label: "Phasen"
},{
key: "tasks",
label: "Aufgaben"
},/*{
key: "forms",
label: "Formulare"
},*/{
key: "documents",
label: "Dokumente"
},{
key: "timetracking",
label: "Zeiterfassung"
},{
key: "events",
label: "Termine"
},{
key: "material",
label: "Material"
}
]
const timeTableRows = [
{
key:"user",
label: "Benutzer"
},{
key:"start",
label: "Start"
},{
key:"end",
label:"Ende"
},{
key:"duration",
label: "Dauer"
},{
key: "type",
label: "Typ"
},{
key:"notes",
label: "Notizen"
},
]
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
name: "",
customer: 0,
users: [dataStore.activeProfile.id]
})
const oldItemInfo = ref({})
const tags = dataStore.getDocumentTags
const phasesTemplates = ref([])
const plants = ref([])
const contracts = ref([])
const projecttypes = ref([])
const disableCustomerSelection =ref(false)
//Functions
const setupPage = async() => {
plants.value = await useSupabaseSelect("plants")
contracts.value = await useSupabaseSelect("contracts")
projecttypes.value = await useSupabaseSelect("projecttypes", "*", "id")
if(mode.value === "show" ){
itemInfo.value = await useSupabaseSelectSingle("projects",route.params.id,"*, customer(*), plant(*), createddocuments(*)")
} else if (mode.value === "edit") {
itemInfo.value = await useSupabaseSelectSingle("projects",route.params.id,"*")
}
if(mode.value === "create") {
let query = route.query
if(query.customer) itemInfo.value.customer = Number(query.customer)
if(query.plant) {
itemInfo.value.plant = Number(query.plant)
itemInfo.value.customer = dataStore.getPlantById(itemInfo.value.plant).customer
}
}
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
if(!oldItemInfo.value.projecttype) {
itemInfo.value.projecttype = projecttypes.value[0].id
}
}
setupPage()
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/projects/show/${itemInfo.value.id}`)
} else {
router.push(`/projects/`)
}
}
const projectHours = () => {
let hours = 0
dataStore.getTimesByProjectId(itemInfo.value.id).forEach(item => {
hours += Number(dayjs(item.end).diff(item.start,'hour',true).toFixed(2))
})
return hours.toFixed(2)
}
const updateProject = async () => {
let initialPhases = projecttypes.value.find(i => i.id === itemInfo.value.projecttype).initialPhases
if(oldItemInfo.value.phases.length === 0) {
itemInfo.value.phases = initialPhases
}
await dataStore.updateItem('projects',itemInfo.value,oldItemInfo.value)
}
const createProject = async () => {
let initialPhases = projecttypes.value.find(i => i.id === itemInfo.value.projecttype).initialPhases
await dataStore.createNewItem('projects',{...itemInfo.value, phases: initialPhases })
}
const changeActivePhase = async (key) => {
let item = await useSupabaseSelectSingle("projects",itemInfo.value.id,'*')
console.log(key)
let phaseLabel = ""
item.phases = item.phases.map(p => {
if(p.active) p.active = false
if(p.key === key) {
p.active = true
p.activated_at = dayjs().format()
p.activated_by = dataStore.activeProfile.id
phaseLabel = p.label
}
return p
})
console.log(item.phases)
await supabase.from("projects").update({phases: item.phases}).eq("id",item.id)
const {error} = await supabase.from("historyitems").insert({
createdBy: dataStore.activeProfile.id,
tenant: dataStore.currentTenant,
text: `Aktive Phase zu "${phaseLabel}" gewechselt`,
project: item.id
})
console.log(error)
setupPage()
}
const renderedPhases = computed(() => {
if(itemInfo.value.phases) {
return itemInfo.value.phases.map((phase,index,array) => {
let isAvailable = false
if(phase.active) {
isAvailable = true
} else if(index > 0 && array[index-1].active ){
isAvailable = true
} else if(index > 1 && array[index-1].optional && array[index-2].active){
isAvailable = true
} else if(array.findIndex(i => i.active) > index) {
isAvailable = true
}
return {
...phase,
label: phase.optional ? `${phase.label}(optional)`: phase.label,
disabled: !isAvailable,
defaultOpen: phase.active ? true : false
}
})
} else {
return []
}
})
const invoiceDeliveryNotes = () => {
router.push(`/createDocument/edit?type=invoices&linkedDocuments=[${itemInfo.value.createddocuments.filter(i => i.type === "deliveryNotes").map(i => i.id)}]`)
}
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Projekt erstellen' : 'Projekt bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/projects`)"
>
Projekte
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
class="text-xl font-medium"
>{{itemInfo.name ? `Projekt: ${itemInfo.name}` : (mode === 'create' ? 'Projekt erstellen' : 'Projekt bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="updateProject"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="createProject"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/projects/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UTabs
:items="tabItems"
v-if="itemInfo.id && mode == 'show'"
v-model="openTab"
>
<template #item="{ item }">
<div v-if="item.key === 'information'" class="flex flex-row">
<div class="w-1/2 mr-3">
<UCard class="mt-5">
<div class="text-wrap">
<p>Kunde: <nuxt-link :to="`/customers/show/${itemInfo.customer.id}`">{{itemInfo.customer.name}}</nuxt-link></p>
<p>Objekt: <nuxt-link v-if="itemInfo.plant" :to="`/plants/show/${itemInfo.plant.id}`">{{itemInfo.plant ? itemInfo.plant.name : ""}}</nuxt-link><span v-else>-</span></p>
<p class="">Notizen: {{itemInfo.notes}}</p>
</div>
</UCard>
<UCard class="mt-3">
<h1 class="font-bold text-lg mb-3">Beteiligte Benutzer:</h1>
<UAlert
v-for="projectUser in itemInfo.profiles"
:avatar="{ alt: dataStore.getProfileById(projectUser).fullName }"
:title="dataStore.getProfileById(projectUser).fullName"
class="mb-3"
/>
</UCard>
</div>
<div class="w-1/2">
<UCard class="mt-5">
<HistoryDisplay
type="project"
v-if="itemInfo"
:element-id="itemInfo.id"
render-headline
/>
</UCard>
</div>
</div>
<div v-if="item.key === 'phases'" class="space-y-3">
<UCard class="mt-5">
<UAccordion
:items="renderedPhases"
>
<template #default="{item,index,open}">
<UButton
variant="ghost"
:color="item.active ? 'primary' : 'white'"
class="mb-1"
:disabled="true"
>
<template #leading>
<div class="w-6 h-6 flex items-center justify-center -my-1">
<UIcon :name="item.icon" class="w-4 h-4 " />
</div>
</template>
<span class="truncate"> {{item.label}}</span>
<template #trailing>
<UIcon
name="i-heroicons-chevron-right-20-solid"
class="w-5 h-5 ms-auto transform transition-transform duration-200"
:class="[open && 'rotate-90']"
/>
</template>
</UButton>
</template>
<template #item="{item, index}">
<UCard class="mx-5">
<template #header>
<span class="text-black">{{item.label}}</span>
</template>
<InputGroup>
<UButton
v-if="!item.activated_at && index !== 0 "
@click="changeActivePhase(item.key)"
>
Phase aktivieren
</UButton>
<UButton
v-if="item.active"
v-for="button in item.quickactions"
@click="router.push(`${button.link}&customer=${itemInfo.customer.id}&project=${itemInfo.id}`)"
>
{{button.label}}
</UButton>
</InputGroup>
<div>
<p v-if="item.activated_at" class="text-black">Aktiviert am: {{dayjs(item.activated_at).format("DD.MM.YY HH:mm")}} Uhr</p>
<p v-if="item.activated_by" class="text-black">Aktiviert durch: {{dataStore.getProfileById(item.activated_by).fullName}}</p>
<p v-if="item.description" class="text-black">Beschreibung: {{item.description}}</p>
</div>
</UCard>
</template>
</UAccordion>
</UCard>
</div>
<div v-if="item.key === 'tasks'" class="space-y-3">
<UCard class="mt-5">
<Toolbar>
<UButton
@click="router.push(`/tasks/create?project=${itemInfo.id}`)"
>
+ Aufgabe
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getTasksByProjectId(itemInfo.id)"
:columns="[{key: 'name',label: 'Name'},{key: 'categorie',label: 'Kategorie'},{key: 'user',label: 'Benutzer'}]"
@select="(row) => router.push(`/tasks/show/${row.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Aufgaben' }"
>
<template #user-data="{row}">
{{dataStore.profiles.find(i => i.id === row.user) ? dataStore.profiles.find(i => i.id === row.user).fullName : ""}}
</template>
</UTable>
</UCard>
</div>
<div v-else-if="item.key === 'documents'" class="space-y-3">
<UCard class="mt-5">
<Toolbar>
<DocumentUpload
type="project"
:element-id="itemInfo.id"
/>
<UButton
@click="router.push(`/createDocument/edit?project=${itemInfo.id}&customer=${itemInfo.customer.id}`)"
>
+ Dokument
</UButton>
<UButton
@click="invoiceDeliveryNotes"
>
Lieferscheine abrechnen
</UButton>
</Toolbar>
<UTable
:rows="itemInfo.createddocuments"
:columns="[
{
label: 'Typ',
key: 'type'
}, {
label: 'Status',
key: 'state'
}, {
label: 'Dokumentennummer',
key: 'documentNumber'
}, {
label: 'Ansprechpartner',
key: 'createdBy'
}
]"
@select="(row) => row.state === 'Entwurf' ? router.push(`/createDocument/edit/${row.id}`) : router.push(`/createDocument/show/${row.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen erstellten Dokumente' }"
>
<template #type-data="{row}">
<span v-if="row.type === 'invoices'">Rechnung</span>
<span v-if="row.type === 'quotes'">Angebot</span>
<span v-if="row.type === 'deliveryNotes'">Lieferschein</span>
</template>
<template #createdBy-data="{row}">
{{dataStore.getProfileById(row.createdBy).fullName}}
</template>
</UTable>
<DocumentList :documents="dataStore.getDocumentsByProjectId(itemInfo.id)"/>
</UCard>
</div>
<div v-else-if="item.key === 'timetracking'" class="space-y-3">
<UCard class="mt-5">
Projekt Zeit: {{String(projectHours()).replace(".",",")}} Stunden
<UTable
:rows="dataStore.getTimesByProjectId(itemInfo.id)"
:columns="timeTableRows"
:empty-state="{ icon: 'i-heroicons-clock', label: 'Noch keine Zeiten in diesem Projekt' }"
>
<template #user-data="{row}">
{{dataStore.profiles.find(profile => profile.id === row.user) ? dataStore.profiles.find(profile => profile.id === row.user).fullName : row.user }}
</template>
<template #duration-data="{row}">
{{(row.start && row.end) ? `${String(dayjs(row.end).diff(row.start,'hour',true).toFixed(2)).replace(".",",")} h` : ""}}
</template>
<template #start-data="{row}">
{{dayjs(row.start).format("DD.MM.YY HH:mm")}}
</template>
<template #end-data="{row}">
{{dayjs(row.end).format("DD.MM.YY HH:mm")}}
</template>
</UTable>
</UCard>
</div>
<div v-else-if="item.key === 'events'" class="space-y-3">
<UCard class="mt-5">
<Toolbar>
<UButton
@click="router.push(`/events/edit`)"
>
+ Termin
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getEventsByProjectId(itemInfo.id)"
@select="(i) => router.push(`/events/show/${i.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Termine anzuzeigen' }"
:columns="[{key:'title',label:'Titel'},{key:'start',label:'Start'},{key:'end',label:'Ende'},{key:'type',label:'Typ'},{key:'resources',label:'Resourcen'}]"
>
<template #start-data="{row}">
{{dayjs(row.start).format("DD.MM.YY HH:mm")}}
</template>
<template #end-data="{row}">
{{dayjs(row.end).format("DD.MM.YY HH:mm")}}
</template>
<template #resources-data="{row}">
{{row.resources.map(i => i.title).join(", ")}}
</template>
</UTable>
</UCard>
</div>
<div v-else-if="item.key === 'material'" class="space-y-3">
</div>
</template>
</UTabs>
<UForm v-else-if="mode === 'edit' || mode === 'create'" >
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Projektnummer:"
>
<UInput
v-model="itemInfo.projectNumber"
placeholder="Leer lassen für automatisch generierte Nummer"
/>
</UFormGroup>
<UFormGroup
label="Objekt:"
>
<USelectMenu
v-model="itemInfo.plant"
:options="itemInfo.customer ? plants.filter(i => i.customer === itemInfo.customer) : plants"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
@change="itemInfo.customer = plants.find(i => i.id === itemInfo.plant).customer,
disableCustomerSelection = true"
>
<template #label>
{{plants.find(i => i.id === itemInfo.plant) ? plants.find(i => i.id === itemInfo.plant).name : "Objekt auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Vertrag:"
>
<USelectMenu
v-model="itemInfo.contract"
:options="itemInfo.customer ? contracts.filter(i => i.customer === itemInfo.customer) : contracts"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
@change="itemInfo.customer = contracts.find(i => i.id === itemInfo.contract).customer,
disableCustomerSelection = true"
>
<template #label>
{{contracts.find(i => i.id === itemInfo.contract) ? contracts.find(i => i.id === itemInfo.contract).name : "Vertrag auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Kundennummer:"
>
<USelectMenu
v-model="itemInfo.customer"
:options="dataStore.customers"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
:disabled="disableCustomerSelection"
>
<template #label>
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kunde auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<USelectMenu
v-model="itemInfo.projecttype"
:options="projecttypes"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['label']"
:disabled="oldItemInfo.projecttype"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Gewerk:"
>
<USelectMenu
v-model="itemInfo.measure"
:options="dataStore.getMeasures"
option-attribute="name"
value-attribute="short"
searchable
:search-attributes="['name']"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Beteiligte Benutzer:"
>
<USelectMenu
v-model="itemInfo.profiles"
:options="dataStore.profiles"
option-attribute="fullName"
value-attribute="id"
searchable
multiple
:search-attributes="['fullName']"
>
<template #label>
{{itemInfo.profiles.length > 0 ? itemInfo.profiles.map(i => dataStore.getProfileById(i).fullName).join(", ") : "Kein Benutzer ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="itemInfo.notes"
/>
</UFormGroup>
</UForm>
</UDashboardPanelContent>
</template>
<style scoped>
</style>

View File

@@ -1,51 +0,0 @@
<template>
<EntityList
type="projects"
:items="items"
:filters="filters"
></EntityList>
</template>
<script setup>
import EntityList from "~/components/EntityList.vue";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const router = useRouter()
const items = ref([])
const setupPage = async () => {
let profiles = (await useSupabaseSelect("profiles"))
items.value = (await useSupabaseSelect("projects","*, customer (name), plant(name), projecttype(name, id)","projectNumber")).map(project => {
return {
...project,
profiles: project.profiles.map(x => profiles.find(z => z.id === x).fullName).join(", "),
phase: getActivePhaseLabel(project)
}
})
}
const getActivePhaseLabel = (item) => {
if(item.phases) {
if(item.phases.length > 0) {
let activePhase = item.phases.find(i => i.active)
if(activePhase) {
return activePhase.label
} else {
return ""
}
}
}
}
setupPage()
</script>

View File

@@ -3,7 +3,6 @@ import HistoryDisplay from "~/components/HistoryDisplay.vue";
import DocumentList from "~/components/DocumentList.vue";
import DocumentUpload from "~/components/DocumentUpload.vue";
import {useSupabaseSelect} from "~/composables/useSupabase.js";
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
@@ -11,7 +10,7 @@ definePageMeta({
defineShortcuts({
'backspace': () => {
router.push("/servicecategories")
router.push("/roles")
},
'arrowleft': () => {
if(openTab.value > 0){
@@ -26,35 +25,49 @@ defineShortcuts({
})
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
const role = useRole()
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({})
const itemInfo = ref({
rights: []
})
const openTab = ref(0)
//Functions
const setupPage = async () => {
if(mode.value === "show" || mode.value === "edit"){
itemInfo.value = await useSupabaseSelectSingle("servicecategories",route.params.id,"*")
}
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/servicecategories/show/${itemInfo.value.id}`)
} else {
router.push(`/servicecategories/`)
itemInfo.value = await useSupabaseSelectSingle("roles",route.params.id,"*")
}
}
const rightOptions = computed(() => {
console.log(Object.keys(useRole().generalAvailableRights.value))
return Object.keys(useRole().generalAvailableRights.value).map(i => {
return {
key: i,
label: role.generalAvailableRights.value[i].label
}
})
})
const rightList = computed(() => {
console.log(rightOptions.value)
return itemInfo.value.rights.map(i => {
if(rightOptions.value.find(x => x.key === i)) {
return rightOptions.value.find(x => x.key === i).label
} else {
return i
}
}).join(", ")
})
setupPage()
</script>
@@ -67,32 +80,32 @@ setupPage()
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/servicecategories`)"
@click="router.push(`/roles`)"
>
Leistungskategorien
Artikel
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
class="text-xl font-medium"
>{{itemInfo.name ? `Leistungskategorie: ${itemInfo.name}` : (mode === 'create' ? 'Leistungskategorie erstellen' : 'Leistungskategorie bearbeiten')}}</h1>
>{{itemInfo.name ? `Rolle: ${itemInfo.name}` : (mode === 'create' ? 'Rolle erstellen' : 'Rolle bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('servicecategories',itemInfo)"
@click="dataStore.updateItem('roles',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('servicecategories',itemInfo)"
@click="dataStore.createNewItem('roles',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
@click="router.push(itemInfo.id ? `/roles/show/${itemInfo.value.id}` : `/products/`)"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
@@ -101,7 +114,7 @@ setupPage()
</UButton>
<UButton
v-if="mode === 'show'"
@click=" router.push(`/servicecategories/edit/${itemInfo.id}`)"
@click=" router.push(`/roles/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
@@ -114,14 +127,18 @@ setupPage()
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="mr-5 flex flex-row">
<div v-if="item.label === 'Informationen'" class="mt-5 flex flex-row">
<div class="w-1/2 mr-5">
<UCard>
<table class="w-full">
<tr>
<td>Name: </td>
<td>Name:</td>
<td>{{itemInfo.name}}</td>
</tr>
<tr>
<td>Rechte:</td>
<td>{{rightList}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.description}}</td>
@@ -132,19 +149,14 @@ setupPage()
<div class="w-1/2">
<UCard>
<HistoryDisplay
type="product"
type="role"
v-if="itemInfo"
:element-id="itemInfo.id"
:element-id="id"
render-headline
/>
</UCard>
</div>
</div>
</template>
</UTabs>
<UForm
@@ -159,6 +171,18 @@ setupPage()
autofocus
/>
</UFormGroup>
<UFormGroup
label="Rechte:"
>
<USelectMenu
:options="rightOptions"
value-attribute="key"
option-attribute="label"
v-model="itemInfo.rights"
multiple
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
>

24
pages/roles/index.vue Normal file
View File

@@ -0,0 +1,24 @@
<script setup>
definePageMeta({
middleware: "auth"
})
const items = ref([])
const setup = async () => {
items.value = await useSupabaseSelect("roles","*")
}
setup()
</script>
<template>
<EntityList
:items="items"
type="roles"
/>
</template>
<style scoped>
</style>

View File

@@ -1,139 +0,0 @@
<template>
<UDashboardNavbar title="Leistungskategorien" :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(`/servicecategories/create`)">+ Leistungskategorie</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(`/servicecategories/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Leistungskategorien anzuzeigen' }"
>
<template #name-data="{row}">
<span
v-if="row === filteredRows[selectedItem]"
class="text-primary-500 font-bold">{{row.name}}</span>
<span v-else>
{{row.name}}
</span>
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/servicecategories/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/servicecategories/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("servicecategories","*")
}
setupPage()
const templateColumns = [
{
key: "name",
label: "Name",
sortable: true
},
{
key: "description",
label: "Beschreibung",
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>

View File

@@ -1,265 +0,0 @@
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/services")
},
'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 supabase = useSupabaseClient()
const id = ref(route.params.id ? route.params.id : null )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
unit: 1,
tags: [],
servicecategories: []
})
const openTab = ref(0)
const servicecategories = ref([])
const units = ref([])
//Functions
const setupPage = async () => {
if(mode.value === "show"){
itemInfo.value = await useSupabaseSelectSingle("services",useRoute().params.id, "*")
} else if(mode.value === "edit") {
itemInfo.value = await useSupabaseSelectSingle("services",useRoute().params.id)
}
servicecategories.value = await useSupabaseSelect("servicecategories","*")
units.value = (await supabase.from("units").select()).data
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Leistung erstellen' : 'Leistung bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/services`)"
>
Leistungen
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
class="text-xl font-medium"
>{{itemInfo.name ? `Leistung: ${itemInfo.name}` : (mode === 'create' ? 'Leistung erstellen' : 'Leistung bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('services',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('services',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="itemInfo.value ? router.push(`/services/show/${itemInfo.value.id}`) : router.push(`/services/`)"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/services/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="[{label: 'Informationen'},{label: 'Dokumente'}]"
v-if="mode === 'show'"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="mt-5 flex flex-row">
<div class="w-1/2 mr-5">
<UCard>
<table class="w-full">
<tr>
<td>Name:</td>
<td>{{itemInfo.name}}</td>
</tr>
<tr>
<td>Leistungsnummer:</td>
<td>{{itemInfo.serviceNumber}}</td>
</tr>
<tr>
<td>Verkaufpreis:</td>
<td>{{itemInfo.sellingPrice && itemInfo.sellingPrice.toFixed(2).replace(".", ",") + " €"}}</td>
</tr>
<tr>
<td>Einheit:</td>
<td>{{units.find(i => i.id === itemInfo.unit) ? units.find(i => i.id === itemInfo.unit).name : itemInfo.unit}}</td>
</tr>
<tr>
<td>Tags:</td>
<td>{{itemInfo.tags.join(", ")}}</td>
</tr>
<tr>
<td>Leistungskategorien:</td>
<td>{{itemInfo.servicecategories ? itemInfo.servicecategories.map(i => servicecategories.find(x => x.id === i).name).join(", ") : ""}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.description}}</td>
</tr>
</table>
</UCard>
</div>
<div class="w-1/2">
<UCard>
<HistoryDisplay
render-headline
type="product"
v-if="itemInfo"
:element-id="itemInfo.id"
/>
</UCard>
</div>
</div>
<div
v-if="item.label === 'Dokumente'"
class="mt-5"
>
<UCard>
<Toolbar>
<DocumentUpload
type="product"
:element-id="itemInfo.id"
/>
</Toolbar>
<DocumentList :documents="dataStore.getDocumentsByProductId(itemInfo.id)"/>
</UCard>
</div>
</template>
</UTabs>
<UForm
v-else-if="mode == 'edit' || mode == 'create'"
class="p-5"
>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Leistungsnummer:"
>
<UInput
v-model="itemInfo.serviceNumber"
/>
</UFormGroup>
<UFormGroup
label="Einheit:"
>
<USelectMenu
v-model="itemInfo.unit"
:options="dataStore.units"
option-attribute="name"
value-attribute="id"
>
<template #label>
{{dataStore.units.find(unit => unit.id === itemInfo.unit) ? dataStore.units.find(unit => unit.id === itemInfo.unit).name : itemInfo.unit }}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Tags:"
>
<USelectMenu
v-model="itemInfo.tags"
:options="dataStore.ownTenant.tags.products"
multiple
/>
</UFormGroup>
<UFormGroup
label="Leistungskategorie:"
>
<USelectMenu
:options="servicecategories"
option-attribute="name"
value-attribute="id"
v-model="itemInfo.servicecategories"
multiple
>
<template #label>
{{itemInfo.servicecategories && itemInfo.servicecategories.length > 0 ? itemInfo.servicecategories.map(i => servicecategories.find(x => x.id === i).name).join(", ") : "Keine Kategorien ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Verkaufspreis:"
>
<UInput
v-model="itemInfo.sellingPrice"
type="number"
steps="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
:rows="6"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,153 +0,0 @@
<template>
<UDashboardNavbar title="Leistungen" :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(`/services/create`)">+ Leistung</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(`/services/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Leistungen 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 #sellingPrice-data="{row}">
{{row.sellingPrice ? Number(row.sellingPrice).toFixed(2) + " €" : ""}}
</template>
<template #unit-data="{row}">
{{dataStore.units.find(unit => unit.id === row.unit) ? dataStore.units.find(unit => unit.id === row.unit).name : row.unit}}
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/services/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/services/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 supabase = useSupabaseClient()
const router = useRouter()
const items = ref([])
const selectedItem = ref(0)
const setupPage = async () => {
items.value = await useSupabaseSelect("services","*")
}
setupPage()
const templateColumns = [
{
key: "name",
label: "Name",
sortable: true
},
{
key: "unit",
label: "Einheit",
sortable: true
},
{
key: "sellingPrice",
label: "Verkaufspreis",
sortable: true
}/*,
{
key: "tags",
label: "Tags",
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>

View File

@@ -1,5 +1,6 @@
<script setup>
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const supabase = useSupabaseClient()
const toast = useToast()
@@ -43,7 +44,7 @@ const generateLink = async () => {
body: {
method: "generateLink",
institutionId: bankData.value.id,
tenant: dataStore.currentTenant
tenant: profileStore.currentTenant
}
})
@@ -62,7 +63,7 @@ const addAccount = async (account) => {
accountId: account.id,
ownerName: account.owner_name,
iban: account.iban,
tenant: dataStore.currentTenant,
tenant: profileStore.currentTenant,
bankId: account.institution_id
}

View File

@@ -2,6 +2,7 @@
import axios from "axios"
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const createEMailAddress = ref("")
const createEMailType = ref("imap")
@@ -22,7 +23,7 @@ const createAccount = async () => {
body: {
emailAddress: createEMailAddress.value,
accountType: createEMailType.value,
profile: dataStore.activeProfile.id
profile: profileStore.activeProfile.id
}
})
@@ -95,11 +96,14 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
</UModal>
<UDashboardNavbar title="E-Mail Konten">
<template #right>
<UButton
@click="showEmailAddressModal = true"
>
+ E-Mail Konto
</UButton>
<UTooltip title="In der Beta nicht verfügbar">
<UButton
@click="showEmailAddressModal = true"
>
+ E-Mail Konto
</UButton>
</UTooltip>
</template>
</UDashboardNavbar>
<UTable
@@ -110,10 +114,10 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine E-Mail Konten anzuzeigen' }"
>
<template #profiles-data="{row}">
{{row.profiles.map(i => profiles.find(x => x.id === i).fullName).join(", ")}}
{{row.profiles}}
</template>
<template #mailboxes-data="{row}">
{{row.mailboxes.map(i => i.name).join(", ")}}
{{row.mailboxes}}
</template>
</UTable>
</template>

View File

@@ -3,6 +3,7 @@ definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const router = useRouter()
@@ -71,14 +72,14 @@ const isLight = computed({
/>
<InputGroup>
<UBadge
v-for="tag in dataStore.ownTenant.tags.documents"
v-for="tag in profileStore.ownTenant.tags.documents"
>
{{tag}}
</UBadge>
</InputGroup>
{{dataStore.ownTenant.tags}}
{{profileStore.ownTenant.tags}}
</div>
</UCard>

View File

@@ -87,6 +87,7 @@ defineShortcuts({
const router = useRouter()
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const mode = useRoute().params.mode
const openTab = ref(0)
@@ -106,7 +107,7 @@ const renderDemoZPL = () => {
const printLabel = async () => {
await supabase.from("printJobs").insert({
tenant: dataStore.currentTenant,
tenant: profileStore.currentTenant,
rawContent: useGenerateZPL(itemInfo.value.handlebarsZPL,{barcode:"XXX"}),
printerName: "ZD411",
printServer: "0dbe30f3-3008-4cde-8a7c-e785b1c22bfc"

View File

@@ -5,6 +5,7 @@ definePageMeta({
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
@@ -35,19 +36,22 @@ const resources = {
},
confirmationOrders: {
label: "Auftragsbestätigungen"
},
deliveryNotes: {
label: "Lieferscheine"
}
}
const numberRanges = ref(dataStore.ownTenant.numberRanges)
const numberRanges = ref(profileStore.ownTenant.numberRanges)
const updateNumberRanges = async (range) => {
const {data,error} = await supabase
.from("tenants")
.update({numberRanges: numberRanges.value})
.eq('id',dataStore.currentTenant)
.eq('id',profileStore.currentTenant)
await dataStore.fetchOwnTenant()
await profileStore.fetchOwnTenant()
}

View File

@@ -16,12 +16,6 @@ const setupPage = async () => {
>
</UDashboardNavbar>
{{dataStore.ownTenant.ownFields}}
</template>
<style scoped>

View File

@@ -1,6 +1,7 @@
<script setup>
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const itemInfo = ref({
@@ -10,17 +11,17 @@ const itemInfo = ref({
})
const setupPage = async () => {
itemInfo.value = (await supabase.from("tenants").select().eq("id",dataStore.currentTenant).single()).data
itemInfo.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
console.log(itemInfo.value)
}
const features = ref(dataStore.ownTenant.features)
const businessInfo = ref(dataStore.ownTenant.businessInfo)
const features = ref(profileStore.ownTenant.features)
const businessInfo = ref(profileStore.ownTenant.businessInfo)
const updateTenant = async (newData) => {
const {data,error} = await supabase.from("tenants")
.update(newData)
.eq("id",dataStore.currentTenant)
.eq("id",profileStore.currentTenant)
.select()
if (error) console.log(error)
@@ -38,10 +39,6 @@ setupPage()
:items="[
{
label: 'Rechnung & Kontakt'
}/*,{
label: 'Lizenz'
}*/,{
label: 'Funktionen'
}
]"
>

View File

@@ -1,321 +0,0 @@
<script setup>
import axios from "axios";
import ZebraBrowserPrintWrapper from "zebra-browser-print-wrapper"
import HistoryDisplay from "~/components/HistoryDisplay.vue";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
spaceNumber: "",
address: {
streetNumber: "",
city: "",
zip:""
}
})
const spaceTypes = ["Standort","Regalplatz", "Kiste", "Palettenplatz", "Sonstiges"]
const spaceProducts = ref([])
const spaceMovements = ref([])
const spaces = ref([])
//Functions
const setupPage = async () => {
if(mode.value === "show"){
itemInfo.value = await useSupabaseSelectSingle("spaces",route.params.id,"*, parentSpace(*), inventoryitems(*)")
spaceMovements.value = await dataStore.getMovementsBySpace(itemInfo.value.id)
spaceProducts.value = []
spaceMovements.value.forEach(movement => {
if(spaceProducts.value.filter(product => product.id === movement.productId).length === 0) spaceProducts.value.push(dataStore.getProductById(movement.productId))
})
} else if(mode.value === "edit") {
itemInfo.value = await useSupabaseSelectSingle("spaces",route.params.id,"*")
}
if(mode.value === "edit" || mode.value === "create"){
if(itemInfo.value){
spaces.value = (await useSupabaseSelect("spaces",'*')).filter(i => i.id !== itemInfo.value.id)
} else {
spaces.value = (await useSupabaseSelect("spaces",'*'))
}
}
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/spaces/show/${itemInfo.value.id}`)
} else {
router.push(`/spaces/`)
}
}
function getSpaceProductCount(productId) {
let productMovements = spaceMovements.value.filter(movement => movement.productId === productId)
let count = 0;
productMovements.forEach(movement => count += movement.quantity)
return count
}
/*
const printSpaceLabel = async () => {
axios
.post(`http://${dataStore.ownTenant.value.labelPrinterIp}/pstprnt`, `^XA^FO10,20^BCN,100^FD${itemInfo.value.spaceNumber}^XZ` )
.then(console.log)
.catch(console.log)
}
*/
const cityLoading = ref(false)
const setCityByZip = async () => {
cityLoading.value = true
itemInfo.value.address.city = await useZipCheck(itemInfo.value.address.zip)
cityLoading.value = false
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Lagerplatz erstellen' : 'Lagerplatz bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/spaces`)"
>
Lagerplätze
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium']"
>{{itemInfo ? `Lagerplatz: ${itemInfo.spaceNumber} - ${itemInfo.description}` : (mode === 'create' ? 'Lagerplatz erstellen' : 'Lagerplatz bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('spaces',itemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('spaces',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click=" router.push(`/spaces/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UTabs
:items="[{label: 'Informationen'},{label: 'Bestand'},{label: 'Inventarartikel'}]"
v-if="itemInfo && mode === 'show'"
class="p-5"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="flex mt-5">
<div class="w-1/2 mr-5">
<UCard>
<div class="truncate">
<table>
<tr>
<td>Typ:</td>
<td>{{itemInfo.type}}</td>
</tr>
<tr v-if="itemInfo.type !== 'Standort'">
<td>Übergeordneter Lagerplatz:</td>
<td><router-link v-if="itemInfo.parentSpace" :to="`/spaces/show/${itemInfo.parentSpace.id}`">{{itemInfo.parentSpace.spaceNumber}} - {{itemInfo.parentSpace.description}}</router-link></td>
</tr>
<tr v-if="itemInfo.type === 'Standort'">
<td>Adresse:</td>
<td>{{`${itemInfo.address.streetNumber}, ${itemInfo.address.zip} ${itemInfo.address.city}`}}</td>
</tr>
<tr>
<td>Beschreibung:</td>
<td>{{itemInfo.description}}</td>
</tr>
</table>
</div>
</UCard>
</div>
<div class="w-1/2">
<UCard>
<HistoryDisplay
type="space"
v-if="itemInfo"
render-headline
:element-id="itemInfo.id"
/>
</UCard>
</div>
</div>
<div v-else-if="item.label === 'Bestand'">
<UCard>
<div v-if="spaceProducts.length > 0">
<table class="w-full">
<tr>
<th class="text-left">Artikel in diesem Lagerplatz</th>
<th>Anzahl</th>
<th>Einheit</th>
</tr>
<tr v-for="product in spaceProducts">
<td>{{product.name}}</td>
<td>{{getSpaceProductCount(product.id)}}</td>
<td>{{dataStore.units.find(unit => unit.id === product.unit).name}}</td>
</tr>
</table>
</div>
<p v-else>Es befinden sich keine Artikel in diesem Lagerplatz</p>
</UCard>
</div>
<div v-else-if="item.label === 'Inventarartikel'">
<UAlert
description="Hier werden alle Inventarartikel in diesem Lagerplatz aufgelistet"
color="primary"
variant="outline"
/>
<UTable
:rows="itemInfo.inventoryitems"
:columns="[{label:'Name',key:'name'}]"
@select="(row) => router.push(`/inventoryitems/show/${row.id}`)"
>
</UTable>
</div>
</template>
</UTabs>
<UForm
v-else-if="mode === 'edit' || mode === 'create'"
class="p-5"
>
<div class="flex flex-row">
<div class="w-1/2 mr-5">
<UDivider>
Allgemeines
</UDivider>
<UFormGroup
label="Typ:"
>
<USelectMenu
:options="spaceTypes"
v-model="itemInfo.type"
>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Übergeordneter Lagerplatz:"
v-if="itemInfo.type !== 'Standort'"
>
<USelectMenu
:options="spaces"
option-attribute="spaceNumber"
value-attribute="id"
v-model="itemInfo.parentSpace"
>
</USelectMenu>
</UFormGroup>
</div>
<div class="w-1/2" v-if="itemInfo.type === 'Standort'">
<UDivider>
Ort
</UDivider>
<UFormGroup
label="Straße + Hausnummer:"
>
<UInput
v-model="itemInfo.address.streetNumber"
/>
</UFormGroup>
<UFormGroup
label="PLZ + Ort:"
>
<InputGroup class="w-full">
<UInput
v-model="itemInfo.address.zip"
placeholder="PLZ"
@focusout="setCityByZip"
/>
<UInput
v-model="itemInfo.address.city"
placeholder="Ort"
class="flex-auto"
:disabled="cityLoading"
/>
</InputGroup>
</UFormGroup>
</div>
</div>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
/>
</UFormGroup>
</UForm>
</UDashboardPanelContent>
</template>
<style scoped>
td,th {
padding-right: 2em;
text-align: left;
}
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,139 +0,0 @@
<template>
<UDashboardNavbar title="Lagerplätze" :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(`/spaces/create`)">+ Lagerplatz</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(`/spaces/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Lagerplätze anzuzeigen' }"
>
<template #spaceNumber-data="{row}">
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.spaceNumber}}</span>
<span v-else>{{row.spaceNumber}}</span>
</template>
<template #address-data="{row}">
<span>{{row.address.streetNumber ? `${row.address.streetNumber}, ${row.address.zip}${row.address.city}` : ''}}</span>
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/spaces/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/spaces/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("spaces","*","spaceNumber")
}
setupPage()
const templateColumns = [
{
key: 'spaceNumber',
label: "Lagerplatznr.",
sortable: true
},
{
key: "type",
label: "Typ",
sortable: true
},
{
key: "description",
label: "Beschreibung",
sortable: true
},
{
key: "address",
label: "Adresse",
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>

View File

@@ -0,0 +1,71 @@
<script setup>
const route = useRoute()
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const type = route.params.type
const dataType = dataStore.dataTypes[route.params.type]
const loaded = ref(false)
const mode = ref("list")
const items = ref([])
const item = ref({})
const setupPage = async () => {
if(route.params.mode) mode.value = route.params.mode
if(mode.value === "show") {
//Load Data for Show
item.value = await useSupabaseSelectSingle(type, route.params.id, dataType.supabaseSelectWithInformation || "*")
} else if(mode.value === "edit") {
//Load Data for Edit
const data = JSON.stringify((await supabase.from(type).select().eq("id", route.params.id).single()).data)
//await useSupabaseSelectSingle(type, route.params.id)
item.value = data
} else if(mode.value === "create") {
//Load Data for Create
item.value = JSON.stringify({})
console.log(item.value)
} else if(mode.value === "list") {
//Load Data for List
items.value = await useSupabaseSelect(type, dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn)
}
loaded.value = true
}
setupPage()
</script>
<template>
<EntityShow
v-if="loaded && mode === 'show'"
:type="route.params.type"
:item="item"
@updateNeeded="setupPage"
:key="item"
/>
<EntityEdit
v-else-if="loaded && (mode === 'edit' || mode === 'create')"
:type="route.params.type"
:item="item"
/>
<EntityList
v-else-if="loaded && mode === 'list'"
:type="type"
:items="items"
/>
<UProgress
v-else
animation="carousel"
class="p-5 mt-10"
/>
</template>
<style scoped>
</style>

View File

@@ -1,210 +0,0 @@
<script setup>
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
profile: dataStore.activeProfile.id
})
const oldItemInfo = ref({})
const categories = ["Offen", "In Bearbeitung", "Dringed", "Erledigt"]
//Functions
const setupPage = () => {
if(mode.value === "show" || mode.value === "edit"){
itemInfo.value = dataStore.getTaskById(Number(useRoute().params.id))
}
if(mode.value === "create") {
let query = route.query
if(query.project) itemInfo.value.project = Number(query.project)
if(query.plant) itemInfo.value.plant = Number(query.plant)
}
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
}
const editItem = async () => {
router.push(`/tasks/edit/${itemInfo.value.id}`)
}
const cancelEditorCreate = () => {
if(itemInfo.value) {
router.push(`/tasks/show/${itemInfo.value.id}`)
} else {
router.push(`/tasks/`)
}
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Aufgabe erstellen' : 'Aufgabe bearbeiten')">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/tasks`)"
>
Aufgaben
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium'/*, ... itemInfo.categorie === 'Erledigt' ? ['text-primary'] : ['text-rose-500']*/]"
>{{itemInfo ? `Aufgabe: ${itemInfo.name}` : (mode === 'create' ? 'Aufgabe erstellen' : 'Aufgabe bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('tasks',itemInfo,oldItemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('tasks',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="editItem"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="[{label: 'Informationen'},{label: 'Logbuch'}]"
v-if="itemInfo && mode === 'show'"
class="p-5"
>
<template #item="{item}">
<UCard class="mt-5">
<div v-if="item.label === 'Informationen'">
<div class="truncate">
<p>Kategorie: {{itemInfo.categorie}}</p>
<p v-if="itemInfo.project">Projekt: <nuxt-link :to="`/projects/show/${itemInfo.project}`">{{dataStore.getProjectById(itemInfo.project).name}}</nuxt-link></p>
<p>Beschreibung: <br><pre v-html="itemInfo.description"></pre></p>
</div>
</div>
<div v-else-if="item.label === 'Logbuch'">
<HistoryDisplay
type="task"
v-if="itemInfo"
:element-id="itemInfo.id"
/>
</div>
</UCard>
</template>
</UTabs>
<UForm v-else-if="mode === 'edit' || mode === 'create' " class="p-5">
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Kategorie:"
>
<USelectMenu
v-model="itemInfo.categorie"
:options="categories"
/>
</UFormGroup>
<UFormGroup
label="Benutzer:"
>
<USelectMenu
v-model="itemInfo.profile"
:options="dataStore.profiles"
option-attribute="fullName"
value-attribute="id"
searchable-placeholder="Suche..."
searchable
:search-attributes="['fullName']"
>
<template #label>
{{dataStore.getProfileById(itemInfo.profile) ? dataStore.getProfileById(itemInfo.profile).fullName : "Kein Benutzer ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<USelectMenu
v-model="itemInfo.project"
:options="dataStore.projects"
option-attribute="name"
value-attribute="id"
searchable-placeholder="Suche..."
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.getProjectById(itemInfo.project) ? dataStore.getProjectById(itemInfo.project).name : "Kein Projekt ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Objekt:"
>
<USelectMenu
v-model="itemInfo.plant"
:options="dataStore.plants"
option-attribute="name"
value-attribute="id"
searchable-placeholder="Suche..."
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Kein Objekt ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
</style>

View File

@@ -1,156 +0,0 @@
<template>
<UDashboardNavbar title="Aufgaben" :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(`/tasks/create`)">+ Aufgabe</UButton>
</template>
</UDashboardNavbar>
<UDashboardToolbar>
<template #left>
<UCheckbox
label="Erledigte Anzeigen"
v-model="showDone"
/>
</template>
<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(`/tasks/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Aufgaben anzuzeigen' }"
>
<template #finish-data="{row}">
<UButton
icon="i-heroicons-check"
variant="ghost"
@click="markAsFinished(row)"
/>
</template>
<template #created_at-data="{row}">
{{row.created_at ? dayjs(row.created_at).format("DD.MM.YY HH:mm") : ''}}
</template>
<template #user-data="{row}">
{{dataStore.profiles.find(i => i.id === row.user) ? dataStore.profiles.find(i => i.id === row.user).fullName : ""}}
</template>
<template #project-data="{row}">
{{dataStore.projects.find(i => i.id === row.project) ? dataStore.projects.find(i => i.id === row.project).name : ""}}
</template>
<template #customer-data="{row}">
{{dataStore.customers.find(customer => customer.id === row.customer) ? dataStore.customers.find(customer => customer.id === row.customer).name : "" }}
</template>
<template #plant-data="{row}">
{{dataStore.getPlantById(row.plant) ? dataStore.getPlantById(row.plant).name : "" }}
</template>
</UTable>
</template>
<script setup>
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const router = useRouter()
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/tasks/create")
}
})
const templateColumns = [
/*{
key:"finish"
},*/{
key: "created_at",
label: "Erstellt am:",
sortable: true
},{
key: "name",
label: "Name:",
sortable: true
},{
key: "categorie",
label: "Kategorie:",
sortable: true
},{
key: "description",
label: "Beschreibung:",
sortable: true
},{
key: "user",
label: "Benutzer:",
sortable: true
},{
key: "project",
label: "Projekt:",
sortable: true
},{
key: "plant",
label: "Objekt:",
sortable: true
}
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
const markAsFinished = (item) => {
dataStore.updateItem("tasks", {...item, categorie: "Erledigt"})
}
const searchString = ref('')
const showDone = ref(false)
const filteredRows = computed(() => {
let items = dataStore.tasks
items = items.filter(i => showDone.value === true ? i.categorie === "Erledigt" : i.categorie !== "Erledigt")
return useSearch(searchString.value, items)
})
</script>
<style scoped>
</style>

View File

@@ -1,452 +0,0 @@
<script setup>
import dayjs from "dayjs";
definePageMeta({
middleware: "auth"
})
//TODO: Build User Page
const dataStore = useDataStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
const toast = useToast()
const id = ref(route.params.id ? route.params.id : null )
let currentItem = null
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({})
const tabItems = [
{
label: "Informationen"
},{
label: "Zeiterfassung"
},{
label: "Rechte"
},
]
const timeSelection = ref("Heute")
const timeColumns = [
{
key: "state",
label: "Status"
},{
key: "user",
label: "Benutzer"
},{
key: "start",
label: "Start"
},{
key: "end",
label: "Ende"
},{
key: "notes",
label: "Notizen"
},{
key: "projectId",
label: "Projekt"
},{
key: "duration",
label: "Dauer"
},
]
const filteredTimes = computed(() => {
let rows = dataStore.times
rows = rows.filter(i => i.user === currentItem.id)
if(timeSelection.value === 'Heute') {
rows = rows.filter(i => dayjs().isSame(i.start,'day'))
} else if(timeSelection.value === 'Diese Woche') {
rows = rows.filter(i => dayjs().isSame(i.start,'week'))
} else if(timeSelection.value === 'Dieser Monat') {
rows = rows.filter(i => dayjs().isSame(i.start,'month'))
} else if(timeSelection.value === 'Dieses Jahr') {
rows = rows.filter(i => dayjs().isSame(i.start,'year'))
} else if(timeSelection.value === 'Letztes Jahr') {
rows = rows.filter(i => dayjs().subtract(1,'year').isSame(i.start,'year'))
}
return rows
})
//Functions
const setupPage = () => {
if(mode.value === "show" || mode.value === "edit"){
currentItem = dataStore.getProfileById(useRoute().params.id)
}
itemInfo.value = currentItem
}
const editItem = async () => {
router.push(`/users/edit/${currentItem.id}`)
setupPage()
}
const cancelEditorCreate = () => {
mode.value = "show"
itemInfo.value = {
id: 0,
infoData: {}
}
}
const getDuration = (time) => {
const dez = dayjs(time.end).diff(time.start,'hour',true).toFixed(2)
const hours = Math.floor(dez)
const minutes = Math.floor((dez - hours) * 60)
return {
dezimal: dez,
hours: hours,
minutes: minutes,
composed: `${hours}:${minutes}`
}
}
const getSelectionSum = computed(() => {
let times = filteredTimes.value
let sum = 0
let sumEntwurf = 0
let sumEingereicht = 0
let sumBestaetigt = 0
times.forEach(time => {
let duration = getDuration(time)
sum += Number(duration.dezimal)
if(time.state === 'Entwurf') {
sumEntwurf += Number(duration.dezimal)
} else if(time.state === 'Eingereicht') {
sumEingereicht += Number(duration.dezimal)
} else if(time.state === 'Bestätigt') {
sumBestaetigt += Number(duration.dezimal)
}
})
sum = sum.toFixed(2)
sumEntwurf = sumEntwurf.toFixed(2)
sumEingereicht = sumEingereicht.toFixed(2)
sumBestaetigt = sumBestaetigt.toFixed(2)
return {
sum,
sumEntwurf,
sumEingereicht,
sumBestaetigt
}
})
setupPage()
</script>
<template>
<div>
<UCard v-if="currentItem && mode == 'show'" >
<template #header>
{{currentItem.fullName}}
</template>
<UTabs :items="tabItems">
<template #item="{item}">
<div
v-if="item.label === 'Informationen'"
class="w-full"
>
<InputGroup class="w-full">
<div class="w-1/2 mr-5">
<UFormGroup
label="Anrede"
>
<UInput/>
</UFormGroup>
<InputGroup>
<UFormGroup
label="Vorname:"
class="flex-auto"
>
<UInput
v-model="itemInfo.firstName"
/>
</UFormGroup>
<UFormGroup
label="Nachnahme:"
class="flex-auto"
>
<UInput
v-model="itemInfo.lastName"
/>
</UFormGroup>
</InputGroup>
<UFormGroup
label="Geburtsdatum"
>
<UInput/>
</UFormGroup>
<UFormGroup
label="Benutzername"
>
<UInput
v-model="itemInfo.username"
disabled
/>
</UFormGroup>
</div>
<div class="w-1/2">
<UFormGroup
label="E-Mail"
>
<UInput/>
</UFormGroup>
<InputGroup>
<UFormGroup
label="Telefonnummer:"
class="w-1/2"
>
<UInput/>
</UFormGroup>
<UFormGroup
label="Mobilfunknummer:"
class="w-1/2"
>
<UInput/>
</UFormGroup>
</InputGroup>
<UFormGroup
label="Straße & Hausnummer"
>
<UInput
class="flex-auto"
/>
</UFormGroup>
<InputGroup>
<UFormGroup
label="Postleitzahl:"
class="w-40"
>
<UInput/>
</UFormGroup>
<UFormGroup
label="Ort:"
class="flex-auto"
>
<UInput/>
</UFormGroup>
</InputGroup>
</div>
</InputGroup>
<UButton
class="mt-5"
@click="dataStore.updateItem('users',itemInfo)"
>
Speichern
</UButton>
</div>
<div v-else-if="item.label === 'Zeiterfassung'">
<UButtonGroup>
<UButton
@click="timeSelection = 'Heute'"
:variant="timeSelection === 'Heute' ? 'solid' : 'outline'"
>
Heute
</UButton>
<UButton
@click="timeSelection = 'Diese Woche'"
:variant="timeSelection === 'Diese Woche' ? 'solid' : 'outline'"
>
Diese Woche
</UButton>
<UButton
@click="timeSelection = 'Dieser Monat'"
:variant="timeSelection === 'Dieser Monat' ? 'solid' : 'outline'"
>
Dieser Monat
</UButton>
<UButton
@click="timeSelection = 'Dieses Jahr'"
:variant="timeSelection === 'Dieses Jahr' ? 'solid' : 'outline'"
>
Dieses Jahr
</UButton>
<UButton
@click="timeSelection = 'Letztes Jahr'"
:variant="timeSelection === 'Letztes Jahr' ? 'solid' : 'outline'"
color="rose"
>
Letztes Jahr
</UButton>
</UButtonGroup>
<UButton
class="ml-3"
to="/employees/timetracking"
>
Zur Zeiterfassung
</UButton>
<InputGroup class="w-full">
<div>
Summe: {{getSelectionSum.sum}}<br>
Summe Entwurf: {{getSelectionSum.sumEntwurf}}<br>
Summe Eingereicht: {{getSelectionSum.sumEingereicht}}<br>
Summe Bestätigt: {{getSelectionSum.sumBestaetigt}}<br>
</div>
</InputGroup>
<UTable
:rows="filteredTimes"
:columns="timeColumns"
>
<template #state-data="{row}">
<span
v-if="row.state === 'Entwurf'"
class="text-rose-500"
>{{row.state}}</span>
<span
v-if="row.state === 'Eingereicht'"
class="text-cyan-500"
>{{row.state}}</span>
<span
v-if="row.state === 'Bestätigt'"
class="text-primary-500"
>{{row.state}}</span>
</template>
<template #start-data="{row}">
<div class="text-right">{{dayjs(row.start).format("DD.MM.YY HH:mm")}}</div>
</template>
<template #end-data="{row}">
<div class="text-right">{{dayjs(row.end).format("HH:mm")}}</div>
</template>
<template #user-data="{row}">
{{dataStore.getProfileById(row.user) ? dataStore.getProfileById(row.user).fullName : ""}}
</template>
<template #duration-data="{row}">
<div class="text-right">{{ getDuration(row).composed}} Std</div>
</template>
<template #projectId-data="{row}">
{{dataStore.getProjectById(row.projectId) ? dataStore.getProjectById(row.projectId).name : ""}}
</template>
</UTable>
</div>
</template>
</UTabs>
<template #footer>
<UButton
v-if="mode == 'show' && currentItem.id"
@click="editItem"
>
Bearbeiten
</UButton>
</template>
</UCard>
<UCard v-else-if="mode == 'edit' || mode == 'create'" >
<template #header>
{{itemInfo.title}}
</template>
<UFormGroup
label="Titel:"
>
<UInput
v-model="itemInfo.title"
/>
</UFormGroup>
<UFormGroup
label="Status:"
>
<USelectMenu
v-model="itemInfo.state"
:options="states"
/>
</UFormGroup>
<UFormGroup
label="Kundennummer:"
>
<USelectMenu
v-model="itemInfo.customer"
:options="dataStore.customers"
option-attribute="name"
value-attribute="id"
searchable
:search-attributes="['name']"
>
<template #label>
{{dataStore.customers.find(customer => customer.id === itemInfo.customer) ? dataStore.customers.find(customer => customer.id === itemInfo.customer).name : "Kunde auswählen"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
>
<UTextarea
v-model="itemInfo.description"
/>
</UFormGroup>
<template #footer>
<UButton
v-if="mode == 'edit'"
@click="dataStore.updateItem('users',{...itemInfo, fullName: `${itemInfo.firstName} ${itemInfo.lastName}`})"
>
Speichern
</UButton>
<!-- <UButton
v-else-if="mode == 'create'"
@click="dataStore.createItem()"
>
Erstellen
</UButton>-->
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
>
Abbrechen
</UButton>
</template>
</UCard>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,43 +0,0 @@
<script setup lang="ts">
definePageMeta({
middleware: "auth"
})
const dataStore = useDataStore()
const router = useRouter()
const columns = [
{
key:"fullName",
label: "Name",
},/*
{
key: "username",
label: "Benutzername"
},*/
{
key: "role",
label: "Rolle"
}
]
</script>
<template>
<UTable
:rows="dataStore.profiles"
:columns="columns"
@select="(item) => router.push(`/users/show/${item.id}`)"
>
</UTable>
<DevOnly>
{{dataStore.profiles}}
</DevOnly>
</template>
<style scoped>
</style>

View File

@@ -1,405 +0,0 @@
<script setup>
import dayjs from "dayjs";
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/vehicles")
},
'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: "",
licensePlate: "",
type: "",
driver: null,
active: true
})
const oldItemInfo = ref({})
const tabItems = [{
label: 'Informationen',
}, {
label: 'Dokumente',
}, {
label: 'Überprüfungen',
}]
const incomingInvoicesColumns = [
{
key: "state",
label: "Status",
sortable: true
},{
key: "vendor",
label: "Lieferant",
sortable: true
},{
key: "date",
label: "Datum",
sortable: true
},{
key: "description",
label: "Beschreibung",
sortable: true
},{
key: "accounts",
label: "Betrag",
sortable: true
},
]
//Functions
const setupPage = async () => {
if(mode.value === "show"){
itemInfo.value = await useSupabaseSelectSingle("vehicles",route.params.id,"*, checks(*)")
} else if(mode.value === "edit"){
itemInfo.value = await useSupabaseSelectSingle("vehicles",route.params.id,"*")
}
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
}
const cancelEditorCreate = () => {
if(itemInfo.value.id) {
router.push(`/vehicles/show/${itemInfo.value.id}`)
} else {
router.push(`/vehicles`)
}
}
const getRowAmount = (row) => {
let amount = 0
row.accounts.forEach(account => {
amount += account.amountNet
amount += account.amountTax
})
return amount
}
setupPage()
</script>
<template>
<UDashboardNavbar
:title="itemInfo ? itemInfo.licensePlate : (mode === 'create' ? 'Fahrzeug erstellen' : 'Fahrzeug bearbeiten')"
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/vehicles`)"
>
Fahrzeuge
</UButton>
</template>
<template #center>
<h1
v-if="itemInfo"
:class="['text-xl','font-medium']"
>{{itemInfo ? `Fahrzeug: ${itemInfo.licensePlate}` : (mode === 'create' ? 'Fahrzeug erstellen' : 'Fahrzeug bearbeiten')}}</h1>
</template>
<template #right>
<ButtonWithConfirm
color="rose"
variant="outline"
@confirmed="dataStore.updateItem('vehicles',{...itemInfo, archived: true})"
v-if="mode === 'edit'"
>
<template #button>
Archivieren
</template>
<template #header>
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
</template>
Möchten Sie das Fahrzeug {{itemInfo.name}} wirklich archivieren?
</ButtonWithConfirm>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('vehicles',itemInfo, oldItemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('vehicles',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="cancelEditorCreate"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/vehicles/edit/${itemInfo.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="tabItems"
v-if="mode === 'show' && itemInfo.id"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="flex flex-row mt-5">
<UCard class="w-1/2 mr-5">
<UAlert
v-if="itemInfo.archived"
color="rose"
variant="outline"
title="Objekt archiviert"
icon="i-heroicons-light-bulb"
class="mb-5"
/>
<div class="text-wrap">
<table class="w-full">
<tr>
<td>Kennzeichen: </td>
<td>{{itemInfo.licensePlate}}</td>
</tr>
<tr>
<td>Typ: </td>
<td>{{itemInfo.type}}</td>
</tr>
<tr>
<td>Fahrgestellnummer: </td>
<td>{{itemInfo.vin}}</td>
</tr>
<tr>
<td>Fahrer:</td>
<td>{{dataStore.profiles.find(profile => profile.id === itemInfo.driver) ? dataStore.profiles.find(profile => profile.id === itemInfo.driver).fullName : 'Kein Fahrer gewählt'}}</td>
</tr>
<tr>
<td>Tankvolumen:</td>
<td>{{itemInfo.tankSize !== 0 ? `${itemInfo.tankSize} L` : "Kein Tank verbaut"}}</td>
</tr>
<tr>
<td>Baujahr:</td>
<td>{{itemInfo.buildYear}}</td>
</tr>
<tr>
<td>Anhänglast:</td>
<td>{{itemInfo.towingCapacity}}</td>
</tr>
<tr>
<td>Farbe:</td>
<td>{{itemInfo.color}}</td>
</tr>
<tr>
<td>Leistung:</td>
<td>{{itemInfo.powerInKW && itemInfo.powerInKW + " kW"}}</td>
</tr>
</table>
</div>
</UCard>
<UCard class="w-1/2">
<HistoryDisplay
type="vehicle"
v-if="itemInfo"
:element-id="itemInfo.id"
/>
</UCard>
</div>
<div v-else-if="item.label === 'Eingangsrechnungen'">
<UTable
:rows="dataStore.getIncomingInvoicesByVehicleId(itemInfo.id)"
:columns="incomingInvoicesColumns"
@select="(row) => router.push('/receipts/show/' + row.id)"
>
<template #vendor-data="{row}">
{{dataStore.getVendorById(row.vendor) ? dataStore.getVendorById(row.vendor).name : ""}}
</template>
<template #date-data="{row}">
{{dayjs(row.date).format("DD.MM.YYYY")}}
</template>
<template #accounts-data="{row}">
{{getRowAmount(row) ? String(getRowAmount(row).toFixed(2)).replace('.',',') + " €" : ""}}
</template>
</UTable>
</div>
<div v-else-if="item.label === 'Dokumente'">
<UCard class="mt-5">
<Toolbar>
<DocumentUpload
type="vehicle"
:element-id="itemInfo.id"
/>
</Toolbar>
<DocumentList
:documents="dataStore.getDocumentsByVehicleId(itemInfo.id)"
/>
</UCard>
</div>
<div v-else-if="item.label === 'Überprüfungen'">
<UCard class="mt-5">
<UTable
:rows="itemInfo.checks"
:columns="[{key:'name',label: 'Name'},{key:'rhythm',label: 'Rhythmus'},{key:'description',label: 'Beschreibung'}]"
class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/checks/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Überprüfungen anzuzeigen' }"
>
<template #rhythm-data="{row}">
{{row.distance}}
<span v-if="row.distanceUnit === 'dayjs'">Tage</span>
<span v-if="row.distanceUnit === 'years'">Jahre</span>
</template>
</UTable>
</UCard>
</div>
</template>
</UTabs>
<UForm
v-else-if="mode == 'edit' || mode == 'create'"
class="p-5"
>
<UFormGroup
label="Kennzeichen:"
>
<UInput
v-model="itemInfo.licensePlate"
/>
</UFormGroup>
<UFormGroup
label="Fahrzeug aktiv:"
>
<UCheckbox
v-model="itemInfo.active"
/>
</UFormGroup>
<UFormGroup
label="Fahrgestellnummer:"
>
<UInput
v-model="itemInfo.vin"
/>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<UTextarea
v-model="itemInfo.type"
/>
</UFormGroup>
<UFormGroup
label="Fahrer:"
>
<USelectMenu
v-model="itemInfo.driver"
:options="[{id: null, fullName: 'Kein Fahrer'},...dataStore.profiles]"
option-attribute="fullName"
value-attribute="id"
>
<template #label>
{{dataStore.profiles.find(profile => profile.id === itemInfo.driver) ? dataStore.profiles.find(profile => profile.id === itemInfo.driver).fullName : 'Kein Fahrer ausgewählt'}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Tankvolumen:"
>
<UInput
v-model="itemInfo.tankSize"
type="number"
>
<template #trailing>
L
</template>
</UInput>
</UFormGroup>
<UFormGroup
label="Baujahr:"
>
<UInput
v-model="itemInfo.buildYear"
type="number"
/>
</UFormGroup>
<UFormGroup
label="Anhängelast:"
>
<UInput
v-model="itemInfo.towingCapacity"
type="number"
>
<template #trailing>kg</template>
</UInput>
</UFormGroup>
<UFormGroup
label="Farbe:"
>
<UInput
v-model="itemInfo.color"
type="text"
/>
</UFormGroup>
<UFormGroup
label="Leistung:"
>
<UInput
v-model="itemInfo.powerInKW"
type="number"
>
<template #trailing>kW</template>
</UInput>
</UFormGroup>
</UForm>
</template>
<style scoped>
td {
border-bottom: 1px solid lightgrey;
vertical-align: top;
padding-bottom: 0.15em;
padding-top: 0.15em;
}
</style>

View File

@@ -1,141 +0,0 @@
<template>
<UDashboardNavbar title="Fahrzeuge" :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(`/vehicles/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Fahrzeuge anzuzeigen' }"
>
<template #active-data="{row}">
<UIcon
:name="row.active ? 'i-heroicons-check' : 'i-heroicons-x-mark'"
:class="['w-5', 'h-5', ... row.active ? ['text-primary-500'] : ['text-rose-600'] ]"
/>
</template>
<template #licensePlate-data="{row}">
<span v-if="row === filteredRows[selectedItem]" class="font-bold text-primary-500">{{row.licensePlate}}</span>
<span v-else>{{row.licensePlate}}</span>
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/vehicles/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/vehicles/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("vehicles","*")
}
setupPage()
const templateColumns = [
{
key: 'active',
label: "Aktiv:",
sortable: true
},{
key: 'licensePlate',
label: "Kennzeichen:",
sortable: true
},{
key: 'vin',
label: "Identifikationnr.:",
sortable: true
},
{
key: "type",
label: "Typ:",
sortable: true
}
]
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)
})
</script>
<style scoped>
</style>

View File

@@ -1,266 +0,0 @@
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'backspace': () => {
router.push("/vendors")
},
'arrowleft': () => {
if(openTab.value > 0){
openTab.value -= 1
}
},
'arrowright': () => {
if(openTab.value < 1) {
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)
let currentItem = ref(null)
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
infoData: {}
})
const oldItemInfo = ref({})
//Functions
const setupPage = () => {
if (mode.value === "show" || mode.value === "edit") {
currentItem.value = dataStore.getVendorById(Number(useRoute().params.id))
}
if (mode.value === "edit") itemInfo.value = currentItem.value
if(currentItem.value) oldItemInfo.value = JSON.parse(JSON.stringify(currentItem.value))
}
const setCityByZip = async () => {
itemInfo.value.infoData.city = await useZipCheck(itemInfo.value.infoData.zip)
}
setupPage()
</script>
<template>
<UDashboardNavbar
:title="currentItem ? `Lieferant: ${currentItem.name}` : (mode === 'create' ? 'Lieferant erstellen' : 'Lieferant bearbeiten')"
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/vendors`)"
>
Lieferanten
</UButton>
</template>
<template #center>
<h1
v-if="currentItem"
:class="['text-xl','font-medium']"
>{{currentItem ? `Lieferant: ${currentItem.name}` : (mode === 'create' ? 'Lieferant erstellen' : 'Lieferant bearbeiten')}}</h1>
</template>
<template #right>
<UButton
v-if="mode === 'edit'"
@click="dataStore.updateItem('vendors',itemInfo,oldItemInfo)"
>
Speichern
</UButton>
<UButton
v-else-if="mode === 'create'"
@click="dataStore.createNewItem('vendors',itemInfo)"
>
Erstellen
</UButton>
<UButton
@click="router.push(currentItem.id ? `/vendors/show/${currentItem.id}` : '/vendors/show')"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
>
Abbrechen
</UButton>
<UButton
v-if="mode === 'show'"
@click="router.push(`/vendors/edit/${currentItem.id}`)"
>
Bearbeiten
</UButton>
</template>
</UDashboardNavbar>
<UTabs
:items="[{label: 'Informationen'},{label: 'Dokumente'}]"
v-if="currentItem && mode == 'show'"
class="p-5"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Informationen'" class="mt-5 flex">
<div class="w-1/2 mr-5">
<UCard>
<div v-if="currentItem.infoData" class="text-wrap">
<p>Straße + Hausnummer: {{currentItem.infoData.street ? currentItem.infoData.street : ""}}</p>
<p>PLZ + Ort: {{currentItem.infoData.zip}} {{currentItem.infoData.city}}</p>
<p>Telefon: {{currentItem.infoData.tel}}</p>
<p>E-Mail: {{currentItem.infoData.email}}</p>
<p>Web: {{currentItem.infoData.web}}</p>
<p>USt-Id: {{currentItem.infoData.ustid}}</p>
<p>Notizen:<br> {{currentItem.notes}}</p>
</div>
</UCard>
<UCard class="mt-5">
<Toolbar>
<UButton
@click="router.push(`/contacts/create?vendor=${currentItem.id}`)"
>
+ Ansprechpartner
</UButton>
</Toolbar>
<UTable
:rows="dataStore.getContactsByVendorId(currentItem.id)"
@select="(row) => router.push(`/contacts/show/${row.id}`)"
:columns="[{label: 'Anrede', key: 'salutation'},{label: 'Name', key: 'fullName'},{label: 'Rolle', key: 'role'}]"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Ansprechpartner' }"
>
</UTable>
</UCard>
</div>
<div class="w-1/2">
<UCard class="h-full">
<HistoryDisplay
type="vendor"
v-if="currentItem"
:element-id="currentItem.id"
:render-headline="true"
/>
</UCard>
</div>
</div>
<UCard class="mt-5" v-else>
<div v-if="item.label === 'Dokumente'">
<InputGroup>
<DocumentUpload
type="vendor"
:element-id="currentItem.id"
/>
</InputGroup>
<DocumentList
:documents="dataStore.getDocumentsByVendorId(currentItem.id)"
/>
</div>
</UCard>
</template>
</UTabs>
<UForm
v-else-if="mode === 'edit' || mode === 'create'"
class="p-5"
>
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Lieferantennr.:"
>
<UInput
v-model="itemInfo.vendorNumber"
placeholder="Leer lassen für automatisch generierte Nummer"
/>
</UFormGroup>
<UFormGroup
label="Straße + Hausnummer"
>
<UInput
v-model="itemInfo.infoData.street"
/>
</UFormGroup>
<UFormGroup
label="Postleitzahl"
>
<UInput
v-model="itemInfo.infoData.zip"
@focusout="setCityByZip"
/>
</UFormGroup>
<UFormGroup
label="Ort"
>
<UInput
v-model="itemInfo.infoData.city"
/>
</UFormGroup>
<UFormGroup
label="Land"
>
<USelectMenu
:options="['Deutschland','Niederlande','Belgien','Italien', 'Frankreich','Irland','USA','Spanien', 'Schweden']"
v-model="itemInfo.infoData.country"
/>
</UFormGroup>
<UFormGroup
label="Telefon:"
>
<UInput
v-model="itemInfo.infoData.tel"
/>
</UFormGroup>
<UFormGroup
label="Notizen:"
>
<UTextarea
v-model="itemInfo.notes"
/>
</UFormGroup>
<UFormGroup
label="E-Mail:"
>
<UInput
v-model="itemInfo.infoData.email"
/>
</UFormGroup>
<UFormGroup
label="Webseite:"
>
<UInput
v-model="itemInfo.infoData.web"
/>
</UFormGroup>
<UFormGroup
label="USt-Id:"
>
<UInput
v-model="itemInfo.infoData.ustid"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
</style>

View File

@@ -1,131 +0,0 @@
<template>
<UDashboardNavbar title="Lieferanten" :badge="filteredRows.length">
<template #right>
<UInput
id="searchinput"
name="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(`/vendors/create/`)">+ Lieferant</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"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
class="w-full"
@select="(i) => router.push(`/vendors/show/${i.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Lieferanten 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 #address-data="{row}">
{{row.infoData.street ? `${row.infoData.street}, ` : ''}}{{row.infoData.special ? `${row.infoData.special},` : ''}} {{(row.infoData.zip || row.infoData.city) ? `${row.infoData.zip} ${row.infoData.city}, ` : ''}} {{row.infoData.country}}
</template>
</UTable>
</template>
<script setup>
definePageMeta({
middleware: "auth"
})
defineShortcuts({
'/': () => {
//console.log(searchinput)
//searchinput.value.focus()
document.getElementById("searchinput").focus()
},
'+': () => {
router.push("/vendors/create")
},
'Enter': {
usingInput: true,
handler: () => {
router.push(`/vendors/show/${filteredRows.value[selectedItem.value].id}`)
}
},
'arrowdown': () => {
selectedItem.value += 1
},
'arrowup': () => {
selectedItem.value -= 1
}
})
const dataStore = useDataStore()
const router = useRouter()
const mode = ref("show")
const items = ref([])
const selectedItem = ref(0)
const setupPage = async () => {
items.value = await useSupabaseSelect("vendors","*","vendorNumber")
}
setupPage()
const templateColumns = [
{
key: 'vendorNumber',
label: "Lieferantennr.",
sortable: true
},
{
key: "name",
label: "Name",
sortable: true
},
{
key: "address",
label: "Adresse",
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>

View File

@@ -3,6 +3,7 @@ import dayjs from "dayjs";
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const toast = useToast()
@@ -12,7 +13,7 @@ const mode = ref(route.params.mode || "show")
const itemInfo = ref({
startDate: new Date(),
endDate: new Date(),
profile: dataStore.activeProfile.id
profile: profileStore.activeProfile.id
})
const oldItemInfo = ref({})
@@ -88,7 +89,7 @@ setupPage()
label="Mitarbeiter:"
>
<USelectMenu
:options="dataStore.profiles"
:options="profileStore.profiles"
v-model="itemInfo.profile"
option-attribute="fullName"
value-attribute="id"

View File

@@ -6,6 +6,7 @@ import isBetween from "dayjs/plugin/isBetween";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter"
import isSameOrBefore from "dayjs/plugin/isSameOrBefore"
import {useCreateWorkingTimesPdf} from "~/composables/useWorkingTimePDFGenerator.js";
import {useFunctions} from "~/composables/useFunctions.js";
dayjs.extend(customParseFormat)
dayjs.extend(isoWeek)
dayjs.extend(isBetween)
@@ -15,6 +16,7 @@ dayjs.extend(isSameOrBefore)
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const route = useRoute()
const router = useRouter()
@@ -25,18 +27,27 @@ const oldItemInfo = ref({})
const workingtimes = ref([])
const absencerequests = ref([])
const setupPage = async () => {
if(route.params.id) itemInfo.value = dataStore.getProfileById(route.params.id)
if(itemInfo.value.id) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
workingtimes.value = (await supabase.from("workingtimes").select().eq("profile",itemInfo.value.id).order("startDate",{ascending:false})).data
absencerequests.value = (await supabase.from("absencerequests").select().eq("user",itemInfo.value.id).order("start",{ascending: false})).data
}
const workingTimeInfo = ref(null)
const selectedPresetRange = ref("Dieser Monat")
const selectedStartDay = ref("")
const selectedEndDay = ref("")
const setupPage = async () => {
if(route.params.id) itemInfo.value = profileStore.getProfileById(route.params.id)
if(itemInfo.value.id) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
workingtimes.value = (await supabase.from("workingtimes").select().eq("profile",itemInfo.value.id).order("startDate",{ascending:false})).data
absencerequests.value = (await supabase.from("absencerequests").select().eq("profile",itemInfo.value.id).order("startDate",{ascending: false})).data
await loadWorkingTimeInfo()
}
const loadWorkingTimeInfo = async () => {
workingTimeInfo.value = await useFunctions().getWorkingTimesEvaluationData(route.params.id,selectedStartDay.value,selectedEndDay.value)
openTab.value = 0
}
const changeRange = () => {
let selector = "M"
let subtract = 0
@@ -67,9 +78,10 @@ const changeRange = () => {
selectedEndDay.value = dayjs().subtract(subtract,selector === "isoWeek" ? "week" : selector).endOf(selector).format("YYYY-MM-DD")
openTab.value = 0
loadWorkingTimeInfo()
}
const workingTimeInfo = computed(() => {
/*const workingTimeInfo = computed(() => {
let times = workingtimes.value
@@ -102,13 +114,13 @@ const workingTimeInfo = computed(() => {
//console.log(times.filter(i => i.approved).length)
//console.log(sumWorkingMinutesApproved)
let recreationDays = ["2024-01-01","2024-05-01","2024-10-03","2024-10-31","2024-12-25","2024-12-26"]
let recreationDays = ["2025-01-01","2025-04-18","2025-04-21","2025-05-01","2025-05-29","2025-06-09","2024-10-03","2024-10-31","2024-12-25","2024-12-26"]
//Feiertagsausgleich
let sumWorkingMinutesRecreationDays = 0
let sumRecreationDays = 0
recreationDays.filter(i => dayjs(i).isSameOrAfter(selectedStartDay.value) && dayjs(i).isSameOrBefore(selectedEndDay.value)).forEach(day => {
let compensationTime = itemInfo.value.weeklyRegularWorkingHours[dayjs(day).day()]
let compensationTime = itemInfo.value.weeklyRegularWorkingHours[dayjs(day).day()] || 0
sumWorkingMinutesRecreationDays += compensationTime * 60
sumRecreationDays++
})
@@ -122,18 +134,24 @@ const workingTimeInfo = computed(() => {
return dayjs(date).isSameOrAfter(start) && dayjs(date).isSameOrBefore(end)
}
absencerequests.value.filter(i => (dayjs(i.start).isBetween(dayjs(selectedStartDay.value),dayjs(selectedEndDay.value)) || dayjs(i.end).isBetween(dayjs(selectedStartDay.value),dayjs(selectedEndDay.value)) ) && i.reason === "Urlaub" && i.approved === "Genehmigt").forEach(absenceRequest => {
absencerequests.value.filter(i => (dayjs(i.startDate).isBetween(dayjs(selectedStartDay.value),dayjs(selectedEndDay.value)) || dayjs(i.endDate).isBetween(dayjs(selectedStartDay.value),dayjs(selectedEndDay.value)) ) && (i.reason === "Urlaub" || i.reason === "Berufsschule") && i.approved === "Genehmigt").forEach(absenceRequest => {
let durationInDays = 0
if(isBetween(absenceRequest.start,selectedStartDay.value,selectedEndDay.value) && isBetween(absenceRequest.end,selectedStartDay.value,selectedEndDay.value)) {
console.log(absenceRequest)
if(isBetween(absenceRequest.startDate,selectedStartDay.value,selectedEndDay.value) && isBetween(absenceRequest.endDate,selectedStartDay.value,selectedEndDay.value)) {
//Full in Range
durationInDays = dayjs(absenceRequest.end).diff(absenceRequest.start, "days") + 1
} else if(isBetween(absenceRequest.start,selectedStartDay.value,selectedEndDay.value) && !isBetween(absenceRequest.end,selectedStartDay.value,selectedEndDay.value)) {
console.log("Full in Range")
durationInDays = dayjs(absenceRequest.endDate).diff(absenceRequest.startDate, "days") + 1
console.log(durationInDays)
} else if(isBetween(absenceRequest.startDate,selectedStartDay.value,selectedEndDay.value) && !isBetween(absenceRequest.endDate,selectedStartDay.value,selectedEndDay.value)) {
//Start in Range
durationInDays = dayjs(selectedEndDay.value).diff(absenceRequest.start, "days") + 1
} else if(!isBetween(absenceRequest.start,selectedStartDay.value,selectedEndDay.value) && isBetween(absenceRequest.end,selectedStartDay.value,selectedEndDay.value)) {
console.log("Start in Range")
durationInDays = dayjs(selectedEndDay.value).diff(absenceRequest.startDate, "days") + 1
} else if(!isBetween(absenceRequest.startDate,selectedStartDay.value,selectedEndDay.value) && isBetween(absenceRequest.endDate,selectedStartDay.value,selectedEndDay.value)) {
//End in Range
durationInDays = dayjs(absenceRequest.end).diff(selectedStartDay.value, "days") + 1
console.log("End in Range")
durationInDays = dayjs(absenceRequest.endDate).diff(selectedStartDay.value, "days") + 1
}
sumVacationDays += durationInDays
@@ -146,7 +164,7 @@ const workingTimeInfo = computed(() => {
//Saldo
let saldo = (sumWorkingMinutesApproved + sumWorkingMinutesRecreationDays +sumWorkingMinutesVacationDays - workingMinutesTarget).toFixed(2)
let saldoInOfficial = (sumWorkingMinutesApproved + sumWorkingMinutesEingereicht + sumWorkingMinutesRecreationDays + sumWorkingMinutesVacationDays - workingMinutesTarget).toFixed(2)
let saldoInOfficial = (sumWorkingMinutesEingereicht + sumWorkingMinutesRecreationDays + sumWorkingMinutesVacationDays - workingMinutesTarget).toFixed(2)
return {
monthlyWorkingMinutes,
@@ -161,7 +179,7 @@ const workingTimeInfo = computed(() => {
saldoInOfficial,
times,
}
})
})*/
const getDuration = (time) => {
const minutes = Math.floor(dayjs(time.endDate).diff(dayjs(time.startDate),'minutes',true))
@@ -177,13 +195,13 @@ const getDuration = (time) => {
const showDocument = ref(false)
const uri = ref("")
const generateDocument = async () => {
const ownTenant = dataStore.ownTenant
const ownTenant = profileStore.ownTenant
const path = ownTenant.letterheadConfig["workingTimesEvaluation"]
const {data,error} = await supabase.storage.from("files").download(path)
uri.value = await useCreateWorkingTimesPdf({
profile: dataStore.getProfileById(route.params.id).fullName,
profile: profileStore.getProfileById(route.params.id).fullName,
...workingTimeInfo.value}, await data.arrayBuffer())
//alert(uri.value)
showDocument.value = true
@@ -274,7 +292,7 @@ changeRange()
/>
<template #panel="{ close }">
<LazyDatePicker v-model="selectedStartDay" @close="openTab = 0" />
<LazyDatePicker v-model="selectedStartDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</UFormGroup>
@@ -286,43 +304,44 @@ changeRange()
/>
<template #panel="{ close }">
<LazyDatePicker v-model="selectedEndDay" @close="openTab = 0" />
<LazyDatePicker v-model="selectedEndDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</UFormGroup>
</template>
</UDashboardToolbar>
<UTabs
:items="[{label: 'Information'},{label: 'Bericht'}]"
class="p-5 h-100"
@change="onTabChange"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Information'">
<UCard class="truncate my-5">
<template #header>
Zusammenfassung
</template>
<p>Eingreicht: {{Math.floor(workingTimeInfo.sumWorkingMinutesEingereicht/60)}}:{{String(workingTimeInfo.sumWorkingMinutesEingereicht % 60).padStart(2,"0")}} h</p>
<p>Genehmigt: {{Math.floor(workingTimeInfo.sumWorkingMinutesApproved/60)}}:{{String(workingTimeInfo.sumWorkingMinutesApproved % 60).padStart(2,"0")}} h</p>
<p>Feiertagsausgleich: {{Math.floor(workingTimeInfo.sumWorkingMinutesRecreationDays/60)}}:{{String(workingTimeInfo.sumWorkingMinutesRecreationDays % 60).padStart(2,"0")}} h / {{workingTimeInfo.sumRecreationDays}} Tage</p>
<p>Urlaubsausgleich: {{Math.floor(workingTimeInfo.sumWorkingMinutesVacationDays/60)}}:{{String(workingTimeInfo.sumWorkingMinutesVacationDays % 60).padStart(2,"0")}} h / {{workingTimeInfo.sumVacationDays}} Tage</p>
<p>Soll Stunden: {{Math.floor(workingTimeInfo.workingMinutesTarget/60)}}:{{String(workingTimeInfo.workingMinutesTarget % 60 ).padStart(2,"0")}} h</p>
<UDashboardPanelContent>
<UTabs
:items="[{label: 'Information'},{label: 'Bericht'}]"
@change="onTabChange"
v-model="openTab"
>
<template #item="{item}">
<div v-if="item.label === 'Information'">
<UCard class="truncate my-5" v-if="workingTimeInfo">
<template #header>
Zusammenfassung
</template>
<p>Eingreicht: {{Math.floor(workingTimeInfo.sumWorkingMinutesEingereicht/60)}}:{{String(workingTimeInfo.sumWorkingMinutesEingereicht % 60).padStart(2,"0")}} h</p>
<p>Genehmigt: {{Math.floor(workingTimeInfo.sumWorkingMinutesApproved/60)}}:{{String(workingTimeInfo.sumWorkingMinutesApproved % 60).padStart(2,"0")}} h</p>
<p>Feiertagsausgleich: {{Math.floor(workingTimeInfo.sumWorkingMinutesRecreationDays/60)}}:{{String(workingTimeInfo.sumWorkingMinutesRecreationDays % 60).padStart(2,"0")}} h / {{workingTimeInfo.sumRecreationDays}} Tage</p>
<p>Urlaubs-/Berufsschulausgleich: {{Math.floor(workingTimeInfo.sumWorkingMinutesVacationDays/60)}}:{{String(workingTimeInfo.sumWorkingMinutesVacationDays % 60).padStart(2,"0")}} h / {{workingTimeInfo.sumVacationDays}} Tage</p>
<p>Soll Stunden: {{Math.floor(workingTimeInfo.workingMinutesTarget/60)}}:{{String(workingTimeInfo.workingMinutesTarget % 60 ).padStart(2,"0")}} h</p>
<!-- <p>Abwesend: </p>
<!-- <p>Abwesend: </p>
<p>Ausgleich:</p>
-->
<p>Inoffizielles Saldo: {{Math.sign(workingTimeInfo.saldoInOfficial) === 1 ? "" : "-"}}{{Math.floor(Math.abs(workingTimeInfo.saldoInOfficial/60))}}:{{String(Math.abs(workingTimeInfo.saldoInOfficial) % 60).padStart(2,"0")}} h</p>
<p>Saldo: {{Math.sign(workingTimeInfo.saldo) === 1 ? "" : "-"}}{{Math.floor(Math.abs(workingTimeInfo.saldo/60))}}:{{String(Math.abs(workingTimeInfo.saldo) % 60).padStart(2,"0")}} h</p>
</UCard>
<p>Ausgleich:</p>
-->
<p>Inoffizielles Saldo(eingereichte Stunden): {{Math.sign(workingTimeInfo.saldoInOfficial) === 1 ? "" : "-"}}{{Math.floor(Math.abs(workingTimeInfo.saldoInOfficial/60))}}:{{String(Math.abs(workingTimeInfo.saldoInOfficial) % 60).padStart(2,"0")}} h</p>
<p>Saldo(genehmigte Stunden): {{Math.sign(workingTimeInfo.saldo) === 1 ? "" : "-"}}{{Math.floor(Math.abs(workingTimeInfo.saldo/60))}}:{{String(Math.abs(workingTimeInfo.saldo) % 60).padStart(2,"0")}} h</p>
</UCard>
<div style="overflow-y: scroll; height: 45vh">
<UTable
:rows="workingTimeInfo.times"
@select="(row) => router.push(`/workingtimes/edit/${row.id}`)"
:columns="[
<div style="overflow-y: scroll; height: 45vh">
<UTable
v-if="workingTimeInfo"
:rows="workingTimeInfo.times"
@select="(row) => router.push(`/workingtimes/edit/${row.id}`)"
:columns="[
{
key: 'state',
label: 'Status'
@@ -343,45 +362,40 @@ changeRange()
label: 'Notizen'
}
]"
>
<template #profile-data="{row}">
{{dataStore.profiles.find(profile => profile.id === row.profile) ? dataStore.profiles.find(profile => profile.id === row.profile).fullName : row.profile }}
</template>
<template #approved-data="{row}">
<span v-if="row.approved" class="text-primary-500">Ja</span>
<span v-else class="text-rose-600">Nein</span>
</template>
>
<template #profile-data="{row}">
{{profileStore.profiles.find(profile => profile.id === row.profile) ? profileStore.profiles.find(profile => profile.id === row.profile).fullName : row.profile }}
</template>
<template #approved-data="{row}">
<span v-if="row.approved" class="text-primary-500">Ja</span>
<span v-else class="text-rose-600">Nein</span>
</template>
<template #start-data="{row}">
{{dayjs(row.startDate).format("HH:mm DD.MM.YY")}} Uhr
</template>
<template #end-data="{row}">
{{dayjs(row.endDate).format("HH:mm DD.MM.YY")}} Uhr
</template>
<template #duration-data="{row}">
{{getDuration(row).composed}}
</template>
</UTable>
</div>
<template #start-data="{row}">
{{dayjs(row.startDate).format("HH:mm DD.MM.YY")}} Uhr
</template>
<template #end-data="{row}">
{{dayjs(row.endDate).format("HH:mm DD.MM.YY")}} Uhr
</template>
<template #duration-data="{row}">
{{getDuration(row).composed}}
</template>
</UTable>
</div>
</div>
<div v-else-if="item.label === 'Bericht'">
<UProgress animation="carousel" v-if="!showDocument"/>
<object
:data="uri"
v-else
type="application/pdf"
class="w-full previewDocument"
/>
</div>
</template>
</UTabs>
<UDashboardPanelContent>
<div v-else-if="item.label === 'Bericht'">
<UProgress animation="carousel" v-if="!showDocument"/>
<object
:data="uri"
v-else
type="application/pdf"
class="w-full previewDocument"
/>
</div>
</template>
</UTabs>
</UDashboardPanelContent>
</template>

View File

@@ -9,20 +9,14 @@ definePageMeta({
})
const dataStore = useDataStore()
const profileStore = useProfileStore()
const supabase = useSupabaseClient()
const user = useSupabaseUser()
const toast = useToast()
const router = useRouter()
const timeInfo = ref({
profile: "",
start: "",
end: null,
notes: null,
})
const filterUser = ref(dataStore.activeProfile.id || "")
const filterUser = ref(profileStore.activeProfile.id || "")
const workingtimes = ref([])
@@ -43,15 +37,6 @@ const filteredRows = computed(() => {
times = times.filter(i => i.profile === filterUser.value)
/*if(dataStore.hasRight('viewTimes')) {
if(filterUser.value !== "") {
times = times.filter(i => i.profile === filterUser.value)
}
} else if(dataStore.hasRight('viewOwnTimes')) {
times = times.filter(i => i.profile === dataStore.getOwnProfile.id)
} else {
times = []
}*/
return times/*.map(i => {
return {
@@ -115,55 +100,9 @@ const columns = [
}
]
console.log(dataStore.workingtimes)
const runningTimeInfo = ref({})
const startTime = async () => {
console.log("started")
timeInfo.value = {
profile: dataStore.activeProfile.id,
startDate: dayjs(),
tenant: dataStore.currentTenant,
state: "Im Web gestartet"
}
const {data,error} = await supabase
.from("workingtimes")
.insert([timeInfo.value])
.select()
if(error) {
console.log(error)
} else if(data) {
//timeInfo.value = data[0]
await dataStore.fetchWorkingTimes()
runningTimeInfo.value = timeInfo.value//dataStore.times.find(time => time.profile === dataStore.activeProfile.id && !time.endDate)
}
}
const stopStartedTime = async () => {
runningTimeInfo.value.endDate = dayjs()
runningTimeInfo.value.state = "Im Web gestoppt"
const {data,error} = await supabase
.from("workingtimes")
.update(runningTimeInfo.value)
.eq('id',runningTimeInfo.value.id)
.select()
console.log(data)
if(error) {
console.log(error)
} else {
toast.add({title: "Zeit erfolgreich gestoppt"})
runningTimeInfo.value = {}
dataStore.fetchWorkingTimes()
}
}
if(dataStore.workingtimes.find(time => time.profile === dataStore.activeProfile.id && !time.endDate)) {
runningTimeInfo.value = dataStore.workingtimes.find(time => time.profile === dataStore.activeProfile.id && !time.end)
}
const getDuration = (time) => {
const minutes = Math.floor(dayjs(time.endDate).diff(dayjs(time.startDate),'minutes',true))
@@ -194,17 +133,19 @@ const expand = ref({
row: {}
})
const setEndDate = (row) => {
row.startDate = dayjs(row.startDate).toISOString()
row.endDate = dayjs(row.endDate).set("year", dayjs(row.startDate).get("year")).set("month", dayjs(row.startDate).get("month")).set("date", dayjs(row.startDate).get("date"))
row.endDate = dayjs(row.endDate).toISOString()
}
</script>
<template>
<UDashboardNavbar title="Anwesenheiten">
<template #right>
<UButton
@click="startTime"
:disabled="runningTimeInfo.id "
>
Start
</UButton>
<UButton
@click="router.push(`/workingtimes/edit`)"
>
@@ -216,14 +157,14 @@ const expand = ref({
<template #left>
<USelectMenu
:options="dataStore.profiles"
:options="profileStore.profiles"
option-attribute="fullName"
value-attribute="id"
v-model="filterUser"
@change="setupPage"
>
<template #label>
{{dataStore.getProfileById(filterUser) ? dataStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
{{profileStore.getProfileById(filterUser) ? profileStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
</template>
</USelectMenu>
<UButton
@@ -233,34 +174,6 @@ const expand = ref({
</UButton>
</template>
</UDashboardToolbar>
<div class="mx-3">
<UAlert
v-if="runningTimeInfo.startDate && !runningTimeInfo.endDate"
class="my-3"
title="Laufende Zeit:"
>
<template #description>
<p>Start: {{dayjs(runningTimeInfo.startDate).format("HH:mm")}}</p>
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') + ' min' }}</p>
<UFormGroup
class="mt-2"
label="Notizen:"
>
<UTextarea
v-model="runningTimeInfo.notes"
/>
</UFormGroup>
<UButton
class="mt-3"
@click="stopStartedTime"
:disabled="!runningTimeInfo.id"
>
Stop
</UButton>
</template>
</UAlert>
</div>
<UTable
class="mt-3"
@@ -296,8 +209,6 @@ const expand = ref({
</UTooltip>
</InputGroup>
<div class="p-4" v-if="!row.approved">
<UFormGroup label="Start:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
@@ -307,7 +218,7 @@ const expand = ref({
/>
<template #panel="{ close }">
<LazyDatePicker v-model="row.startDate" @close="row.endDate = row.startDate" mode="dateTime"/>
<LazyDatePicker v-model="row.startDate" @close="setEndDate(row)" mode="dateTime"/>
</template>
</UPopover>
</UFormGroup>
@@ -337,7 +248,7 @@ const expand = ref({
</div>
<div class="px-4 pb-4" v-else>
<p><span class="font-bold">Mitarbeitende/r:</span> {{dataStore.getProfileById(row.profile).fullName}}</p>
<p><span class="font-bold">Mitarbeitende/r:</span> {{profileStore.getProfileById(row.profile).fullName}}</p>
<p><span class="font-bold">Start:</span> {{dayjs(row.startDate).format("DD.MM.YYYY HH:mm")}}</p>
<p><span class="font-bold">Ende:</span> {{dayjs(row.endDate).format("DD.MM.YYYY HH:mm")}}</p>
<p><span class="font-bold">Genehmigt:</span> {{row.approved ? "Ja" : "Nein"}}</p>
@@ -345,7 +256,7 @@ const expand = ref({
</div>
</template>
<template #profile-data="{row}">
{{dataStore.profiles.find(profile => profile.id === row.profile) ? dataStore.profiles.find(profile => profile.id === row.profile).fullName : row.profile }}
{{profileStore.profiles.find(profile => profile.id === row.profile) ? profileStore.profiles.find(profile => profile.id === row.profile).fullName : row.profile }}
</template>
<template #approved-data="{row}">
<span v-if="row.approved" class="text-primary-500">Ja</span>