Added Frontend

This commit is contained in:
2026-01-06 12:09:31 +01:00
250 changed files with 29602 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
import {Capacitor} from "@capacitor/core";
import {Device} from "@capacitor/device";
import {Network} from "@capacitor/network";
const override = false
export const useCapacitor = () => {
const getPlatform = () => {
return Capacitor.getPlatform()
}
const getDeviceInfo = async () => {
return await Device.getInfo()
}
const getIsPhone = async () => {
let deviceInfo = await useCapacitor().getDeviceInfo()
return override || deviceInfo.model.toLowerCase().includes('iphone')
}
const getIsNative = () => {
return override || Capacitor.isNativePlatform()
}
const getNetworkStatus = async () => {
return await Network.getStatus()
}
return {getPlatform, getDeviceInfo, getNetworkStatus, getIsPhone, getIsNative}
}

View File

@@ -0,0 +1,3 @@
export const useCurrency = (value,currencyString = " €") => {
return `${Number(value).toFixed(2).replace(".",",")} ${currencyString}`.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}

View File

@@ -0,0 +1,34 @@
import { createSharedComposable } from '@vueuse/core'
const _useDashboard = () => {
const route = useRoute()
const router = useRouter()
const isHelpSlideoverOpen = ref(false)
const isNotificationsSlideoverOpen = ref(false)
defineShortcuts({
'g-h': () => router.push('/'),
'g-a': () => router.push('/standardEntity/tasks'),
'g-d': () => router.push('/files'),
'g-k': () => router.push('/standardEntity/customers'),
'g-l': () => router.push('/standardEntity/vendors'),
'g-s': () => router.push('/settings'),
'g-p': () => router.push('/standardEntity/projects'),
'g-v': () => router.push('/standardEntity/contracts'),
'g-o': () => router.push('/standardEntity/plants'),
'?': () => isHelpSlideoverOpen.value = !isHelpSlideoverOpen.value,
n: () => isNotificationsSlideoverOpen.value = !isNotificationsSlideoverOpen.value
})
watch(() => route.fullPath, () => {
isHelpSlideoverOpen.value = false
isNotificationsSlideoverOpen.value = false
})
return {
isHelpSlideoverOpen,
isNotificationsSlideoverOpen
}
}
export const useDashboard = createSharedComposable(_useDashboard)

View File

@@ -0,0 +1,220 @@
import { useDataStore } from "~/stores/data"
export const useEntities = (
relation: string,
) => {
const dataStore = useDataStore()
const toast = useToast()
const router = useRouter()
const dataType = dataStore.dataTypes[relation]
const select = async (
select: string = "*",
sortColumn: string | null = null,
ascending: boolean = false,
noArchivedFiltering: boolean = false
) => {
const res = await useNuxtApp().$api(`/api/resource/${relation}`, {
method: "GET",
params: {
select,
sort: sortColumn || undefined,
asc: ascending
}
})
let data = res || []
if (dataType && dataType.isArchivable && !noArchivedFiltering) {
data = data.filter((i: any) => !i.archived)
}
return data
}
const selectPaginated = async (options: {
select?: string
filters?: Record<string, any>
sort?: { field: string; direction?: 'asc' | 'desc' }[]
page?: number
limit?: number
includeArchived?: boolean
noPagination?: boolean,
search?: string,
searchColumns?: string[],
distinctColumns?: string[],
}): Promise<{ data: any[]; meta: any }> => {
const {
select = '*',
filters = {},
sort = [],
page = 1,
limit = 25,
includeArchived = false,
noPagination = false,
search,
searchColumns = [],
distinctColumns = [],
} = options
const queryParams: Record<string, any> = {
select,
page,
limit,
noPagination: noPagination ? 'true' : undefined
}
// --- 🔍 Search-Parameter (optional) ---
if (search && search.trim().length > 0) {
queryParams.search = search.trim()
}
if (searchColumns.length > 0) queryParams.searchColumns = searchColumns.join(',')
if (distinctColumns.length > 0) queryParams.distinctColumns = distinctColumns.join(',')
// --- Sortierung ---
if (sort.length > 0) {
queryParams.sort = sort
.map(s => `${s.field}:${s.direction || 'asc'}`)
.join(',')
}
// --- Filter ---
for (const [key, value] of Object.entries(filters)) {
if (Array.isArray(value)) {
queryParams[`filter[${key}]`] = value.join(',')
} else {
queryParams[`filter[${key}]`] = value
}
}
const response = await useNuxtApp().$api(`/api/resource/${relation}/paginated`, {
method: 'GET',
params: queryParams
})
if (!response) {
return { data: [], meta: null }
}
let data = response.data || []
const meta = response.queryConfig || {}
// --- Optional: Archivierte ausblenden ---
if (!includeArchived) {
data = data.filter((i: any) => !i.archived)
}
return { data, meta }
}
const selectSpecial = async (
select: string = "*",
sortColumn: string | null = null,
ascending: boolean = false,
) => {
const res = await useNuxtApp().$api(`/api/resource-special/${relation}`, {
method: "GET",
params: {
select,
sort: sortColumn || undefined,
asc: ascending
}
})
return res || []
}
const selectSingle = async (
idToEq: string | number,
select: string = "*",
withInformation: boolean = true
) => {
if (!idToEq) return null
const res = await useNuxtApp().$api(withInformation ? `/api/resource/${relation}/${idToEq}` : `/api/resource/${relation}/${idToEq}/true`, {
method: "GET",
params: { select }
})
return res
}
const create = async (
payload: Record<string, any>,
noRedirect: boolean = false
) => {
const res = await useNuxtApp().$api(`/api/resource/${relation}`, {
method: "POST",
body: payload
})
toast.add({title: `${dataType.labelSingle} hinzugefügt`})
if(dataType.redirect && !noRedirect) {
if(dataType.isStandardEntity) {
await router.push(dataType.redirectToList ? `/standardEntity/${relation}` : `/standardEntity/${relation}/show/${res.id}`)
} else {
await router.push(dataType.redirectToList ? `/${relation}` : `/${relation}/show/${res.id}`)
}
}
//modal.close() TODO: Modal Close wenn in Modal
return res
}
const update = async (
id: string | number,
payload: Record<string, any>,
noRedirect: boolean = false
) => {
const res = await useNuxtApp().$api(`/api/resource/${relation}/${id}`, {
method: "PUT",
body: payload
})
toast.add({title: `${dataType.labelSingle} geändert`})
if(dataType.redirect && !noRedirect) {
if(dataType.isStandardEntity) {
await router.push(dataType.redirectToList ? `/standardEntity/${relation}` : `/standardEntity/${relation}/show/${res.id}`)
} else {
await router.push(dataType.redirectToList ? `/${relation}` : `/${relation}/show/${res.id}`)
}
}
//modal.close() TODO: Modal Close wenn in Modal
return res
}
/**
* Soft Delete = archived = true
*/
const archive = async (
id: string | number
) => {
const res = await useNuxtApp().$api(`/api/resource/${relation}/${id}`, {
method: "PUT",
body: { archived: true }
})
navigateTo(dataType.isStandardEntity ? `/standardEntity/${relation}` : `/${relation}`)
return res
}
return {select, create, update, archive, selectSingle, selectSpecial, selectPaginated}
}

View File

@@ -0,0 +1,25 @@
export const useErrorLogging = (resourceType) => {
const supabase = useSupabaseClient()
const toast = useToast()
const profileStore = useProfileStore()
const logError = async (error) => {
let errorData = {
message: error,
tenant: profileStore.currentTenant,
profile: profileStore.activeProfile.id
}
const {data:supabaseData,error:supabaseError} = await supabase.from("errors").insert(errorData).select().single()
if(supabaseError) {
console.error(supabaseError)
} else if(supabaseData) {
return supabaseData.id
}
}
return { logError}
}

View File

@@ -0,0 +1,142 @@
export const useFiles = () => {
const supabase = useSupabaseClient()
const toast = useToast()
const auth = useAuthStore()
let bucket = "filesdev"
const uploadFiles = async (fileData, files,tags, upsert) => {
const uploadSingleFile = async (file) => {
//Create File Entry to Get ID for Folder
const formData = new FormData()
formData.append("file", file)
formData.append("meta", JSON.stringify(fileData))
const {fileReturn} = await useNuxtApp().$api("/api/files/upload",{
method: "POST",
body: formData
})
}
if(files.length === 1) {
await uploadSingleFile(files[0])
} else if( files.length > 1) {
for(let i = 0; i < files.length; i++){
await uploadSingleFile(files[i])
}
}
}
const selectDocuments = async (sortColumn = null, folder = null) => {
let data = []
data = await useEntities("files").select("*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)")
const res = await useNuxtApp().$api("/api/files/presigned",{
method: "POST",
body: {
ids: data.map(i => i.id)
}
})
console.log(res)
return res.files
}
const selectSomeDocuments = async (documentIds, sortColumn = null, folder = null) => {
if(documentIds.length === 0) return []
const res = await useNuxtApp().$api("/api/files/presigned",{
method: "POST",
body: {
ids: documentIds
}
})
console.log(res)
return res.files
}
const selectDocument = async (id) => {
let documentIds = [id]
if(documentIds.length === 0) return []
const res = await useNuxtApp().$api("/api/files/presigned",{
method: "POST",
body: {
ids: documentIds
}
})
console.log(res)
return res.files[0]
}
const downloadFile = async (id?: string, ids?: string[], returnAsBlob: Boolean = false) => {
const url = id ? `/api/files/download/${id}` : `/api/files/download`
const body = ids ? { ids } : undefined
const res:any = await useNuxtApp().$api.raw(url, {
method: "POST",
body,
responseType: "blob", // wichtig!
})
// Dateiname bestimmen
let filename = "download"
if (id) {
// Einzeldatei → nimm den letzten Teil des Pfads aus Content-Disposition
const contentDisposition = res.headers?.get("content-disposition")
if (contentDisposition) {
const match = contentDisposition.match(/filename="?([^"]+)"?/)
if (match) filename = match[1]
}
} else {
filename = "dateien.zip"
}
// Direkt speichern
const blob = res._data as Blob
if(returnAsBlob) {
return blob
} else {
const link = document.createElement("a")
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
URL.revokeObjectURL(link.href)
}
}
const dataURLtoFile = (dataurl:string, filename:string) => {
let arr = dataurl.split(","),
//@ts-ignore
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[arr.length - 1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type: mime});
}
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument, downloadFile, dataURLtoFile}
}

View File

@@ -0,0 +1,25 @@
export const useFormatDuration = (durationInMinutes:number,) => {
if (!durationInMinutes || durationInMinutes <= 0) return "00:00"
const hrs = Math.floor(durationInMinutes / 60)
const mins = Math.floor(durationInMinutes % 60)
return `${String(hrs).padStart(2, "0")}:${String(mins).padStart(2, "0")}`
}
export const useFormatDurationDays = (start,end) => {
const startDate = useNuxtApp().$dayjs(start);
const endDate = useNuxtApp().$dayjs(end);
if(startDate.isBefore(endDate)){
// inkl. beider Tage → +1
const days = endDate.diff(startDate, "day") + 1;
return days + " Tag" + (days > 1 ? "e" : "");
} else {
const days = startDate.diff(endDate, "day") + 1;
return days + " Tag" + (days > 1 ? "e" : "");
}
}

View File

@@ -0,0 +1,138 @@
import axios from "axios";
import dayjs from "dayjs";
const baseURL = /*"http://192.168.1.129:3333"*/ /*"http://localhost:3333"*/ "https://functions.fedeo.io"
export const useFunctions = () => {
const supabase = useSupabaseClient()
const getWorkingTimesEvaluationData = async (user_id, startDate, endDate) => {
// Der neue Endpunkt ist /staff/time/evaluation und erwartet die Benutzer-ID als targetUserId Query-Parameter.
// Wir bauen den Query-String zusammen.
const queryParams = new URLSearchParams({
from: startDate,
to: endDate,
targetUserId: user_id, // Die ID wird als targetUserId übergeben
});
// Der neue API-Pfad verwendet nur noch den Basis-Endpunkt.
const url = `/api/staff/time/evaluation?${queryParams.toString()}`;
// Annahme: useNuxtApp().$api führt den GET-Request aus und liefert die Daten zurück.
return (await useNuxtApp().$api(url));
}
const useNextNumber = async (numberRange) => {
return (await useNuxtApp().$api(`/api/functions/usenextnumber/${numberRange}`,)).usedNumber
}
const useCreateTicket = async (subject,message,url,source) => {
const {data:{session:{access_token}}} = await supabase.auth.getSession()
const {data} = await axios({
method: "POST",
url: `${baseURL}/functions/createticket`,
data: {
subject,
message,
source,
url
},
headers: {
Authorization: `Bearer ${access_token}`
}
})
return !!data.ticket_created;
}
const useBankingGenerateLink = async (institutionId) => {
return (await useNuxtApp().$api(`/api/banking/link/${institutionId}`)).link
}
const useCreatePDF = async (data,path,type) => {
const returnData = await useNuxtApp().$api(`/api/functions/pdf/${type}`, {
method: "POST",
body: {
data: data,
backgroundPath: path,
}
})
console.log(returnData)
return `data:${returnData.mimeType};base64,${returnData.base64}`
}
const useZipCheck = async (zip) => {
const returnData = await useNuxtApp().$api(`/api/functions/check-zip/${zip}`, {
method: "GET",
})
return returnData
}
const useGetInvoiceData = async (file) => {
const {data:{session:{access_token}}} = await supabase.auth.getSession()
const {data} = await axios({
method: "POST",
url: `${baseURL}/functions/getinvoicedatafromgpt`,
data: {
file
},
headers: {
Authorization: `Bearer ${access_token}`
}
})
console.log(data)
return data
}
const useSendTelegramNotification = async (message) => {
const {data:{session:{access_token}}} = await supabase.auth.getSession()
const {data,error} = await axios({
method: "POST",
url: `${baseURL}/functions/sendtelegramnotification`,
data: {
message: message
},
headers: {
Authorization: `Bearer ${access_token}`
}
})
if(error){
} else {
return true
}
}
const useBankingCheckInstitutions = async (bic) => {
return await useNuxtApp().$api(`/api/banking/institutions/${bic}`)
}
const useBankingListRequisitions = async (reqId) => {
return await useNuxtApp().$api(`/api/banking/requisitions/${reqId}`)
}
return {getWorkingTimesEvaluationData, useNextNumber, useCreateTicket, useBankingGenerateLink, useZipCheck, useBankingCheckInstitutions, useBankingListRequisitions, useCreatePDF,useGetInvoiceData, useSendTelegramNotification}
}

View File

@@ -0,0 +1,110 @@
// composables/useHelpdeskApi.ts
import { ref } from 'vue'
import { useNuxtApp } from '#app'
export function useHelpdeskApi() {
const { $api } = useNuxtApp()
const loading = ref(false)
const error = ref<string | null>(null)
const base = '/api/helpdesk'
// 🔹 Konversationen abrufen
async function getConversations(status?: string) {
loading.value = true
error.value = null
try {
const query = status ? `?status=${status}` : ''
const data = await $api(`${base}/conversations${query}`)
return data
} catch (err: any) {
error.value = err.message || 'Fehler beim Laden der Konversationen'
return []
} finally {
loading.value = false
}
}
// 🔹 Einzelne Konversation
async function getConversation(id: string) {
try {
return await $api(`${base}/conversations/${id}`)
} catch (err: any) {
error.value = err.message
return null
}
}
// 🔹 Nachrichten einer Konversation
async function getMessages(conversationId: string) {
try {
return await $api(`${base}/conversations/${conversationId}/messages`)
} catch (err: any) {
error.value = err.message
return []
}
}
// 🔹 Neue Nachricht senden
async function sendMessage(conversationId: string, text: string) {
try {
return await $api(`${base}/conversations/${conversationId}/messages`, {
method: 'POST',
body: { text },
})
} catch (err: any) {
error.value = err.message
throw err
}
}
async function replyMessage(conversationId: string, text: string) {
try {
return await $api(`${base}/conversations/${conversationId}/reply`, {
method: 'POST',
body: { text },
})
} catch (err: any) {
error.value = err.message
throw err
}
}
// 🔹 Neuen Kontakt (manuell) anlegen
async function createContact(payload: { email?: string; phone?: string; display_name?: string }) {
try {
return await $api(`${base}/contacts`, {
method: 'POST',
body: payload,
})
} catch (err: any) {
error.value = err.message
throw err
}
}
// 🔹 Konversation-Status ändern
async function updateConversationStatus(conversationId: string, status: string) {
try {
return await $api(`${base}/conversations/${conversationId}/status`, {
method: 'PATCH',
body: { status },
})
} catch (err: any) {
error.value = err.message
throw err
}
}
return {
loading,
error,
getConversations,
getConversation,
getMessages,
sendMessage,
createContact,
updateConversationStatus,
replyMessage,
}
}

View File

@@ -0,0 +1,61 @@
export const useNumberRange = (resourceType) => {
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const numberRanges = profileStore.ownTenant.numberRanges
const numberRange = numberRanges[resourceType]
const useNextNumber = async () => {
let nextNumber = numberRange.nextNumber
let newNumberRanges = numberRanges
newNumberRanges[resourceType].nextNumber += 1
const {data,error} = await supabase
.from("tenants")
.update({numberRanges: newNumberRanges})
.eq('id',profileStore.currentTenant)
await profileStore.fetchOwnTenant()
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
}
return { useNextNumber}
}
/*export const useNumberRange = (resourceType) => {
const supabase = useSupabaseClient()
const {numberRanges} = storeToRefs(useDataStore())
const {fetchNumberRanges} = useDataStore()
const numberRange = numberRanges.value.find(range => range.resourceType === resourceType)
const useNextNumber = async () => {
let nextNumber = numberRange.nextNumber
const {data,error} = await supabase
.from("numberranges")
.update({nextNumber: nextNumber + 1})
.eq('id',numberRange.id)
fetchNumberRanges()
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
}
return { useNextNumber}
}*/

View File

@@ -0,0 +1,9 @@
export function usePermission() {
const auth = useAuthStore()
const has = (key: string) => {
return auth.hasPermission(key)
}
return { has }
}

View File

@@ -0,0 +1,27 @@
import Handlebars from "handlebars";
export const usePrintLabel = async (printServerId,printerName , rawZPL ) => {
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
await supabase.from("printJobs").insert({
tenant: profileStore.currentTenant,
rawContent: rawZPL,
printerName: printerName,
printServer: printServerId
})
}
export const useGenerateZPL = (rawZPL,data) => {
let template = Handlebars.compile(rawZPL)
return template(data)
}

View File

@@ -0,0 +1,309 @@
export const useRole = () => {
const profileStore = useProfileStore()
const generalAvailableRights = ref({
projects: {
label: "Projekte",
showToAllUsers: false
},
"projects-viewAll": {
label: "Alle Projekte einsehen",
parent: "projects"
},
"projects-create": {
label: "Projekte erstellen",
parent: "projects"
},
contracts: {
label: "Verträge",
showToAllUsers: false
},
"contracts-viewAll": {
label: "Alle Verträge einsehen",
parent: "contracts"
},
"contracts-create": {
label: "Verträge erstellen",
parent: "contracts"
},
plants: {
label: "Objekte",
showToAllUsers: false
},
"plants-viewAll": {
label: "Alle Objekte einsehen",
parent: "plants"
},
"plants-create": {
label: "Objekte erstellen",
parent: "plants"
},
products: {
label: "Artikel",
showToAllUsers: true
},
"products-create": {
label: "Artikel erstellen",
parent: "products"
},
productcategories: {
label: "Artikelkategorie",
showToAllUsers: true
},
"productcategories-create": {
label: "Artikelkategorie erstellen",
parent: "productcategories"
},
services: {
label: "Leistungen",
showToAllUsers: true
},
"services-create": {
label: "Leistungen erstellen",
parent: "services"
},
servicecategories: {
label: "Leistungskategorien",
showToAllUsers: true
},
"servicecategories-create": {
label: "Leistungskategorien erstellen",
parent: "servicecategories"
},
customers: {
label: "Kunden",
showToAllUsers: false
},
"customers-viewAll": {
label: "Alle Kunden einsehen",
parent: "customers"
},
"customers-create": {
label: "Kunden erstellen",
parent: "customers"
},
contacts: {
label: "Kontakte",
showToAllUsers: false
},
"contacts-viewAll": {
label: "Alle Kontakte einsehen",
parent: "contacts"
},
"contacts-create": {
label: "Kontakte erstellen",
parent: "contacts"
},
vendors: {
label: "Lieferanten",
showToAllUsers: false
},
"vendors-viewAll": {
label: "Alle Lieferanten einsehen",
parent: "vendors"
},
"vendors-create": {
label: "Lieferanten erstellen",
parent: "vendors"
},
checks: {
label: "Überprüfungen",
showToAllUsers: false
},
"checks-viewAll": {
label: "Alle Überprüfungen einsehen",
parent: "checks"
},
"checks-create": {
label: "Überprüfungen erstellen",
parent: "checks"
},
vehicles: {
label: "Fahrzeuge",
showToAllUsers: false
},
"vehicles-viewAll": {
label: "Alle Fahrzeuge einsehen",
parent: "vehicles"
},
"vehicles-create": {
label: "Fahrzeuge erstellen",
parent: "vehicles"
},
inventoryitems: {
label: "Inventarartikel",
showToAllUsers: false
},
"inventoryitems-viewAll": {
label: "Alle Inventarartikel einsehen",
parent: "inventoryitems"
},
"inventoryitems-create": {
label: "Inventarartikel erstellen",
parent: "inventoryitems"
},
inventoryitemgroups: {
parent: "inventoryitems",
label: "Inventarartikelgruppen",
showToAllUsers: false
},
"inventoryitemgroups-viewAll": {
label: "Alle Inventarartikelgruppen einsehen",
parent: "inventoryitemgroups"
},
"inventoryitemgroups-create": {
label: "Inventarartikelgruppen erstellen",
parent: "inventoryitemgroups"
},
absencerequests: {
label: "Abwesenheiten",
showToAllUsers: false
},
"absencerequests-viewAll": {
label: "Alle Abwesenheiten einsehen",
parent: "absencerequests"
},
"absencerequests-create": {
label: "Abwesenheiten erstellen",
parent: "absencerequests"
},
events: {
label: "Termine",
showToAllUsers: false
},
"events-viewAll": {
label: "Alle Termine einsehen",
parent: "events"
},
"events-create": {
label: "Termine erstellen",
parent: "events"
},
spaces: {
label: "Lagerplätze",
showToAllUsers: false
},
"spaces-viewAll": {
label: "Alle Lagerplätze einsehen",
parent: "spaces"
},
"spaces-create": {
label: "Lagerplätze erstellen",
parent: "spaces"
},
roles: {
label: "Rollen",
showToAllUsers: false
},
"roles-viewAll": {
label: "Alle Rollen einsehen",
parent: "roles"
},
"roles-create": {
label: "Rollen erstellen",
parent: "roles"
},
tasks: {
label: "Aufgaben",
showToAllUsers: false
},
"tasks-viewAll": {
label: "Alle Aufgaben einsehen",
parent: "tasks"
},
"tasks-create": {
label: "Aufgaben erstellen",
parent: "tasks"
},
costcentres: {
label: "Kostenstellen",
showToAllUsers: false
},
"costcentres-viewAll": {
label: "Alle Kostenstellen einsehen",
parent: "costcentres"
},
"costcentres-create": {
label: "Kostenstellen erstellen",
parent: "costcentres"
},
ownaccounts: {
label: "Buchungskonten",
showToAllUsers: false
},
"ownaccounts-viewAll": {
label: "Alle Buchungskonten einsehen",
parent: "ownaccounts"
},
"ownaccounts-create": {
label: "Buchungskonten erstellen",
parent: "ownaccounts"
},
documentboxes: {
label: "Dokuemntenboxen",
showToAllUsers: false
},
"documentboxes-viewAll": {
label: "Alle Dokuemntenboxen einsehen",
parent: "documentboxesx"
},
"documentboxes-create": {
label: "Dokuemntenboxen erstellen",
parent: "documentboxes"
},
hourrates: {
label: "Stundensätze",
showToAllUsers: false
},
"hourrates-viewAll": {
label: "Alle Stundensätze einsehen",
parent: "hourrates"
},
"hourrates-create": {
label: "Stundensätze erstellen",
parent: "hourrates"
},
"inventory": {
label: "Lager",
},
})
let role = profileStore.activeProfile.role
const checkRight = (right) => {
let rightsToCheck = [right]
//console.log(right.split("-"))
if(right.split("-").length > 1) {
rightsToCheck.push(right.split("-")[0])
}
//console.log(rightsToCheck)
let hasAllNeccessaryRights = true
//console.log(role.rights)
rightsToCheck.forEach(i => {
if(!role.rights.includes(i)){
hasAllNeccessaryRights = false
}
})
return hasAllNeccessaryRights
}
return {
role,
generalAvailableRights,
checkRight
}
}

View File

@@ -0,0 +1,15 @@
export const useSearch = (searchString,items) => {
if(!searchString) {
return items
}
return items.filter(i => JSON.stringify(i).toLowerCase().includes(searchString.toLowerCase()))
}
export const useListFilter = (searchString,items,showArchived = false) => {
if(!searchString) {
return items.filter(i => !i.archived)
}
return items.filter(i => JSON.stringify(i).toLowerCase().includes(searchString.toLowerCase()) && !i.archived)
}

View File

@@ -0,0 +1,18 @@
export const useSort = (items,column,direction) => {
if(!searchString) {
return items
}
return items.filter(i => JSON.stringify(i).toLowerCase().includes(searchString.toLowerCase()))
}

View File

@@ -0,0 +1,119 @@
import { defineStore } from 'pinia'
import { useAuthStore } from '~/stores/auth'
export const useStaffTime = () => {
const { $api, $dayjs } = useNuxtApp()
const auth = useAuthStore()
// ... (list Funktion bleibt gleich) ...
const list = async (filter?: { user_id?: string, from?: string, to?: string }) => {
// ... (Code wie zuvor)
const from = filter?.from || $dayjs().startOf('month').format('YYYY-MM-DD')
const to = filter?.to || $dayjs().endOf('month').format('YYYY-MM-DD')
const targetUserId = filter?.user_id || auth.user.id
const params = new URLSearchParams({ from, to, targetUserId })
try {
const spans = await $api(`/api/staff/time/spans?${params.toString()}`)
return (spans || []).map((span: any) => {
const start = $dayjs(span.startedAt)
const end = span.endedAt ? $dayjs(span.endedAt) : $dayjs()
return {
id: span.sourceEventIds && span.sourceEventIds.length > 0 ? span.sourceEventIds[0] : null,
eventIds: span.sourceEventIds || [],
state: span.status,
started_at: span.startedAt,
stopped_at: span.endedAt,
duration_minutes: end.diff(start, 'minute'),
user_id: targetUserId,
type: span.type,
description: span.payload?.description || ''
}
}).sort((a: any, b: any) => $dayjs(b.started_at).valueOf() - $dayjs(a.started_at).valueOf())
} catch (error) {
console.error("Fehler beim Laden:", error)
return []
}
}
/**
* Startet "jetzt" (Live-Modus).
* Kann optional eine Zeit empfangen (für manuelle Korrekturen),
* aber wir nutzen dafür besser die createEntry Funktion unten.
*/
const start = async (description = "Arbeitszeit", time?: string) => {
await $api('/api/staff/time/event', {
method: 'POST',
body: {
eventtype: 'work_start',
eventtime: time || new Date().toISOString(), // 💡 Fix: Zeit akzeptieren
payload: { description }
}
})
}
const stop = async () => {
await $api('/api/staff/time/event', { method: 'POST', body: { eventtype: 'work_end', eventtime: new Date().toISOString() } })
}
const submit = async (entry: any) => {
const ids = entry.eventIds || (entry.id ? [entry.id] : [entry]);
if (!ids || ids.length === 0) return
await $api('/api/staff/time/submit', { method: 'POST', body: { eventIds: ids } })
}
const approve = async (entry: any) => {
if (!entry?.user_id) return
const ids = entry.eventIds || [entry.id];
await $api('/api/staff/time/approve', { method: 'POST', body: { eventIds: ids, employeeUserId: entry.user_id } })
}
const reject = async (entry: any, reason = "Abgelehnt") => {
if (!entry?.user_id) return
const ids = entry.eventIds || [entry.id];
await $api('/api/staff/time/reject', { method: 'POST', body: { eventIds: ids, employeeUserId: entry.user_id, reason } })
}
const update = async (entry: any, newData: { start: string, end: string | null, type: string, description: string }) => {
if (!entry || !entry.eventIds || entry.eventIds.length === 0) {
throw new Error("Bearbeiten fehlgeschlagen: Keine IDs.")
}
await $api('/api/staff/time/edit', {
method: 'POST',
body: {
originalEventIds: entry.eventIds,
newStart: newData.start,
newEnd: newData.end,
newType: newData.type,
description: newData.description,
reason: "Manuelle Bearbeitung"
}
})
}
// 🆕 NEU: Manuellen Eintrag erstellen (Vergangenheit oder Zeitraum)
const createEntry = async (data: { start: string, end: string | null, type: string, description: string }) => {
// 1. Start Event senden
// Wir nutzen den dynamischen Typ (work_start, vacation_start etc.)
await $api('/api/staff/time/event', {
method: 'POST',
body: {
eventtype: `${data.type}_start`,
eventtime: data.start,
payload: { description: data.description }
}
})
// 2. End Event senden (falls vorhanden)
if (data.end) {
await $api('/api/staff/time/event', {
method: 'POST',
body: {
eventtype: `${data.type}_end`,
eventtime: data.end
}
})
}
}
return { list, start, stop, submit, approve, reject, update, createEntry }
}

View File

@@ -0,0 +1,138 @@
export const useSum = () => {
const supabase = useSupabaseClient()
const getIncomingInvoiceSum = (invoice) => {
let sum = 0
invoice.accounts.forEach(account => {
sum += account.amountTax
sum += account.amountNet
})
return sum.toFixed(2)
}
const getCreatedDocumentSum = (createddocument,createddocuments = []) => {
let totalNet = 0
let total19 = 0
let total7 = 0
createddocument.rows.forEach(row => {
if(!['pagebreak','title','text'].includes(row.mode)){
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
totalNet = totalNet + Number(rowPrice)
if(row.taxPercent === 19) {
total19 = total19 + Number(rowPrice * 0.19)
} else if(row.taxPercent === 7) {
total7 = total7 + Number(rowPrice * 0.07)
}
}
})
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
let totalGrossAlreadyPaid = 0
createddocument.usedAdvanceInvoices.forEach(advanceInvoiceId => {
let advanceInvoice = createddocuments.find(i => i.id === advanceInvoiceId)
let priceNet = advanceInvoice.rows.find(i => i.advanceInvoiceData).price
let partSum = priceNet * ((100 + advanceInvoice.rows.find(i => i.advanceInvoiceData).taxPercent) / 100)
totalGrossAlreadyPaid += partSum
})
let sumToPay = totalGross - totalGrossAlreadyPaid
return Number(sumToPay.toFixed(2))
}
const getCreatedDocumentSumDetailed = (createddocument) => {
let totalNet = 0
let total19 = 0
let total7 = 0
createddocument.rows.forEach(row => {
if(!['pagebreak','title','text'].includes(row.mode)){
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
totalNet = totalNet + Number(rowPrice)
if(row.taxPercent === 19) {
total19 = total19 + Number(rowPrice * 0.19)
} else if(row.taxPercent === 7) {
total7 = total7 + Number(rowPrice * 0.07)
}
}
})
//Title Sum
let titleSums = {}
let lastTitle = ""
createddocument.rows.forEach(row => {
if(row.mode === 'title'){
titleSums[`${row.pos} - ${row.text}`] = 0
lastTitle = `${row.pos} - ${row.text}`
} else if(!['pagebreak','text'].includes(row.mode) && lastTitle !== ""){
titleSums[lastTitle] = Number(titleSums[lastTitle]) + Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) )
}
})
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
let totalGrossAlreadyPaid = 0
createddocument.usedAdvanceInvoices.forEach(advanceInvoiceId => {
let advanceInvoice = createddocuments.value.find(i => i.id === advanceInvoiceId)
let priceNet = advanceInvoice.rows.find(i => i.advanceInvoiceData).price
let partSum = priceNet * ((100 + advanceInvoice.rows.find(i => i.advanceInvoiceData).taxPercent) / 100)
totalGrossAlreadyPaid += partSum
})
console.log(totalGrossAlreadyPaid)
let sumToPay = totalGross - totalGrossAlreadyPaid
return {
titleSums: titleSums,
totalNet: totalNet,
total19: total19,
total7: total7,
totalGross: totalGross,
totalGrossAlreadyPaid: totalGrossAlreadyPaid,
totalSumToPay: sumToPay
}
}
const getIsPaid = (createddocument,createddocuments) => {
let amountPaid = 0
createddocument.statementallocations.forEach(allocation => amountPaid += allocation.amount)
return Number(amountPaid.toFixed(2)) === getCreatedDocumentSum(createddocument,createddocuments)
}
return {getIncomingInvoiceSum, getCreatedDocumentSum, getCreatedDocumentSumDetailed, getIsPaid}
}

View File

@@ -0,0 +1,28 @@
import { useDataStore } from "~/stores/data"
export const useUsers = (
id: string | number,
) => {
const dataStore = useDataStore()
const toast = useToast()
const router = useRouter()
const getProfile = async () => {
const res = await useNuxtApp().$api(`/api/profiles/${id}`, {
method: "GET"
})
return res
}
return {getProfile}
}

View File

@@ -0,0 +1,25 @@
import axios from 'axios'
export const useZebraStatus = async (relation,select = '*', sortColumn = null) => {
const {data} = await axios({
method:"get",
url:"http://192.168.2.161/config.html",
})
console.log(data)
}
export const useZebraPstPrnt = async (zpl) => {
try {
const {data} = await axios({
method: "post",
url:"http://192.168.2.161/pstprnt",
data: zpl
})
} catch (error) {
console.log(error)
}
}