Compare commits
30 Commits
b35c991634
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e7554fa2cc | |||
| 7c1fabf58a | |||
| 1203b6cbd1 | |||
| 525f2906fb | |||
| b105382abf | |||
| b1cdec7d17 | |||
| 6b9de04d83 | |||
| f1d512b2e5 | |||
| 529ec0c77d | |||
| 246677b750 | |||
| c839714945 | |||
| 8614917a05 | |||
| 2de80ea6ca | |||
| 6f5fed0ffb | |||
| 767152c535 | |||
| 3128893ba2 | |||
| bcf460cfd5 | |||
| da704be925 | |||
| c049730599 | |||
| 0194345ed8 | |||
| 82d8cd36b9 | |||
| 66110da6c4 | |||
| 267648074c | |||
| 32b4c40e11 | |||
| f044195d86 | |||
| 202e20ddd5 | |||
| 905f2e7bf4 | |||
| b39a52fb20 | |||
| 098bd02808 | |||
| db21b43120 |
19
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
19
.gitea/ISSUE_TEMPLATE/bug_report.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: 🐛 Bug Report
|
||||||
|
about: Erstelle einen Bericht, um uns zu helfen, das Projekt zu verbessern.
|
||||||
|
title: '[BUG] '
|
||||||
|
labels: Problem
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Beschreibung**
|
||||||
|
|
||||||
|
|
||||||
|
**Reproduktion**
|
||||||
|
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
|
||||||
|
|
||||||
|
**Achtung: Achte bitte auf Datenschutz deiner Daten sowie der Daten deiner Kunden. Sollten ein Screenshot nur mit Daten möglich sein, schwärze diese bitte vor dem Upload.**
|
||||||
17
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
17
.gitea/ISSUE_TEMPLATE/feature_request.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: ✨ Feature Request
|
||||||
|
about: Schlage eine Idee für dieses Projekt vor.
|
||||||
|
title: '[FEATURE] '
|
||||||
|
labels: Funktionswunsch
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Ist dein Feature-Wunsch mit einem Problem verbunden?**
|
||||||
|
|
||||||
|
|
||||||
|
**Lösungsvorschlag**
|
||||||
|
|
||||||
|
|
||||||
|
**Alternativen**
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export const projects = pgTable("projects", {
|
|||||||
updatedAt: timestamp("updated_at", { withTimezone: true }),
|
updatedAt: timestamp("updated_at", { withTimezone: true }),
|
||||||
updatedBy: uuid("updated_by").references(() => authUsers.id),
|
updatedBy: uuid("updated_by").references(() => authUsers.id),
|
||||||
|
|
||||||
active_phase: text("active_phase"),
|
active_phase: text("active_phase").default("Erstkontakt"),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Project = typeof projects.$inferSelect
|
export type Project = typeof projects.$inferSelect
|
||||||
|
|||||||
@@ -461,10 +461,9 @@ export default async function resourceRoutes(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(createData).forEach((key) => {
|
Object.keys(createData).forEach((key) => {
|
||||||
if(key.toLowerCase().includes("date")) createData[key] = normalizeDate(createData[key])
|
if(key.toLowerCase().includes("date") && key !== "deliveryDateType") createData[key] = normalizeDate(createData[key])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const [created] = await server.db
|
const [created] = await server.db
|
||||||
.insert(table)
|
.insert(table)
|
||||||
.values(createData)
|
.values(createData)
|
||||||
@@ -513,8 +512,17 @@ export default async function resourceRoutes(server: FastifyInstance) {
|
|||||||
|
|
||||||
let data = {...body, updated_at: new Date().toISOString(), updated_by: userId}
|
let data = {...body, updated_at: new Date().toISOString(), updated_by: userId}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
delete data.updatedBy
|
||||||
|
//@ts-ignore
|
||||||
|
delete data.updatedAt
|
||||||
|
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
if(key.includes("_at") || key.includes("At")) {
|
console.log(key)
|
||||||
|
|
||||||
|
if(key.includes("_at") || key.includes("At") || key.toLowerCase().includes("date")) {
|
||||||
data[key] = normalizeDate(data[key])
|
data[key] = normalizeDate(data[key])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
|||||||
|
|
||||||
server.post("/staff/time/event", async (req, reply) => {
|
server.post("/staff/time/event", async (req, reply) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user.user_id
|
const actorId = req.user.user_id;
|
||||||
const tenantId = req.user.tenant_id
|
const tenantId = req.user.tenant_id
|
||||||
|
|
||||||
const body = req.body as any
|
const body = req.body as any
|
||||||
@@ -35,17 +35,15 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
|||||||
|
|
||||||
const dataToInsert = {
|
const dataToInsert = {
|
||||||
tenant_id: tenantId,
|
tenant_id: tenantId,
|
||||||
user_id: userId,
|
user_id: body.user_id,
|
||||||
actortype: "user",
|
actortype: "user",
|
||||||
actoruser_id: userId,
|
actoruser_id: actorId,
|
||||||
eventtime: normalizeDate(body.eventtime),
|
eventtime: normalizeDate(body.eventtime),
|
||||||
eventtype: body.eventtype,
|
eventtype: body.eventtype,
|
||||||
source: "WEB",
|
source: "WEB",
|
||||||
payload: body.payload // Payload (z.B. Description) mit speichern
|
payload: body.payload // Payload (z.B. Description) mit speichern
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(dataToInsert)
|
|
||||||
|
|
||||||
const [created] = await server.db
|
const [created] = await server.db
|
||||||
.insert(stafftimeevents)
|
.insert(stafftimeevents)
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -390,7 +388,9 @@ export default async function staffTimeRoutes(server: FastifyInstance) {
|
|||||||
const evaluatedUserId = targetUserId || actingUserId;
|
const evaluatedUserId = targetUserId || actingUserId;
|
||||||
|
|
||||||
const startDate = new Date(from);
|
const startDate = new Date(from);
|
||||||
const endDate = new Date(to);
|
let endDateQuery = new Date(to);
|
||||||
|
endDateQuery.setDate(endDateQuery.getDate() + 1);
|
||||||
|
const endDate = endDateQuery;
|
||||||
|
|
||||||
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
||||||
return reply.code(400).send({ error: "Ungültiges Datumsformat." });
|
return reply.code(400).send({ error: "Ungültiges Datumsformat." });
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const resourceConfig = {
|
|||||||
table: contracts,
|
table: contracts,
|
||||||
searchColumns: ["name", "notes", "contractNumber", "paymentType", "sepaRef", "bankingName"],
|
searchColumns: ["name", "notes", "contractNumber", "paymentType", "sepaRef", "bankingName"],
|
||||||
numberRangeHolder: "contractNumber",
|
numberRangeHolder: "contractNumber",
|
||||||
|
mtoLoad: ["customer"],
|
||||||
},
|
},
|
||||||
plants: {
|
plants: {
|
||||||
table: plants,
|
table: plants,
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
FROM node:20-alpine
|
# --- Stage 1: Build ---
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/nuxt-app
|
|
||||||
WORKDIR /usr/src/nuxt-app
|
WORKDIR /usr/src/nuxt-app
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN npm i
|
# Nur Files kopieren, die für die Installation nötig sind (besseres Caching)
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Restlichen Code kopieren und bauen
|
||||||
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# --- Stage 2: Runtime ---
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
|
||||||
|
WORKDIR /usr/src/nuxt-app
|
||||||
|
|
||||||
|
# Von der Build-Stage NUR den fertigen .output Ordner kopieren
|
||||||
|
COPY --from=builder /usr/src/nuxt-app/.output ./.output
|
||||||
|
|
||||||
|
# Optional: Falls du statische Dateien aus public brauchst,
|
||||||
|
# sind diese normalerweise bereits in .output/public enthalten.
|
||||||
|
|
||||||
ENV NUXT_HOST=0.0.0.0
|
ENV NUXT_HOST=0.0.0.0
|
||||||
ENV NUXT_PORT=3000
|
ENV NUXT_PORT=3000
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENTRYPOINT ["node", ".output/server/index.mjs"]
|
ENTRYPOINT ["node", ".output/server/index.mjs"]
|
||||||
@@ -55,7 +55,7 @@ const setup = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
filetypes.value = await useEntities("filetags").select()
|
filetypes.value = await useEntities("filetags").select()
|
||||||
documentboxes.value = await useEntities("documentboxes").select()
|
//documentboxes.value = await useEntities("documentboxes").select()
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ const links = computed(() => {
|
|||||||
icon: "i-heroicons-user-group",
|
icon: "i-heroicons-user-group",
|
||||||
children: [
|
children: [
|
||||||
... true ? [{
|
... true ? [{
|
||||||
label: "Anwesenheiten",
|
label: "Zeiten",
|
||||||
to: "/staff/time",
|
to: "/staff/time",
|
||||||
icon: "i-heroicons-clock",
|
icon: "i-heroicons-clock",
|
||||||
}] : [],
|
}] : [],
|
||||||
|
|||||||
@@ -131,38 +131,5 @@ button {
|
|||||||
.btn-confirm:hover { background: #dc2626; }
|
.btn-confirm:hover { background: #dc2626; }
|
||||||
|
|
||||||
|
|
||||||
/* --------------------------------------------------------- */
|
|
||||||
/* DARK MODE OVERRIDES */
|
|
||||||
/* Wir nutzen :global(.dark), um auf die Klasse im <html>/<body> zuzugreifen */
|
|
||||||
/* --------------------------------------------------------- */
|
|
||||||
:global(.dark) .guard-overlay {
|
|
||||||
background: rgba(0,0,0,0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .guard-modal {
|
|
||||||
background: #1f2937; /* gray-800 */
|
|
||||||
color: #f3f4f6; /* gray-100 */
|
|
||||||
border: 1px solid #374151; /* gray-700 Border für Kontrast */
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .guard-body {
|
|
||||||
color: #9ca3af; /* gray-400 */
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .btn-cancel {
|
|
||||||
background: #374151; /* gray-700 */
|
|
||||||
color: #e5e7eb; /* gray-200 */
|
|
||||||
}
|
|
||||||
:global(.dark) .btn-cancel:hover {
|
|
||||||
background: #4b5563; /* gray-600 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Confirm Button bleibt rot, aber evtl. leicht angepasst */
|
|
||||||
:global(.dark) .btn-confirm {
|
|
||||||
background: #b91c1c; /* red-700 (etwas dunkler für Darkmode angenehmer) */
|
|
||||||
color: #fecaca; /* red-100 Text */
|
|
||||||
}
|
|
||||||
:global(.dark) .btn-confirm:hover {
|
|
||||||
background: #991b1b;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -159,7 +159,16 @@ const submit = async () => {
|
|||||||
@input="e => form.startDate = new Date(e.target.value)"
|
@input="e => form.startDate = new Date(e.target.value)"
|
||||||
/>
|
/>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
<UFormGroup label="Ende">
|
<UFormGroup label="Dauer (Stunden)">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.25"
|
||||||
|
placeholder="z.B. 1.5"
|
||||||
|
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-primary-600 sm:text-sm sm:leading-6 px-2"
|
||||||
|
@input="e => form.endDate = dayjs(form.startDate).add(parseFloat(e.target.value), 'hour').toDate()"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
<UFormGroup label="Ende" class="col-span-2">
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-primary-600 sm:text-sm sm:leading-6 px-2"
|
class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-primary-600 sm:text-sm sm:leading-6 px-2"
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
|||||||
start: startIso, // Die eingegebene Startzeit
|
start: startIso, // Die eingegebene Startzeit
|
||||||
end: endIso, // Die eingegebene Endzeit (oder null)
|
end: endIso, // Die eingegebene Endzeit (oder null)
|
||||||
type: state.type,
|
type: state.type,
|
||||||
description: state.description
|
description: state.description,
|
||||||
|
user_id: props.defaultUserId
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.add({ title: 'Zeit manuell erfasst', color: 'green' })
|
toast.add({ title: 'Zeit manuell erfasst', color: 'green' })
|
||||||
|
|||||||
@@ -91,15 +91,18 @@ export const useStaffTime = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 NEU: Manuellen Eintrag erstellen (Vergangenheit oder Zeitraum)
|
// 🆕 NEU: Manuellen Eintrag erstellen (Vergangenheit oder Zeitraum)
|
||||||
const createEntry = async (data: { start: string, end: string | null, type: string, description: string }) => {
|
const createEntry = async (data: { start: string, end: string | null, type: string, description: string, user_id: string }) => {
|
||||||
// 1. Start Event senden
|
// 1. Start Event senden
|
||||||
// Wir nutzen den dynamischen Typ (work_start, vacation_start etc.)
|
// Wir nutzen den dynamischen Typ (work_start, vacation_start etc.)
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
await $api('/api/staff/time/event', {
|
await $api('/api/staff/time/event', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
eventtype: `${data.type}_start`,
|
eventtype: `${data.type}_start`,
|
||||||
eventtime: data.start,
|
eventtime: data.start,
|
||||||
payload: { description: data.description }
|
payload: { description: data.description },
|
||||||
|
user_id: data.user_id,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -109,7 +112,9 @@ export const useStaffTime = () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
eventtype: `${data.type}_end`,
|
eventtype: `${data.type}_end`,
|
||||||
eventtime: data.end
|
eventtime: data.end,
|
||||||
|
payload: { description: data.description },
|
||||||
|
user_id: data.user_id,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,11 +236,17 @@ const footerLinks = [
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex flex-col gap-3 w-full">
|
<div class="flex flex-col gap-3 w-full">
|
||||||
|
|
||||||
|
|
||||||
|
<UColorModeButton />
|
||||||
<LabelPrinterButton/>
|
<LabelPrinterButton/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Footer Links -->
|
<!-- Footer Links -->
|
||||||
<UDashboardSidebarLinks :links="footerLinks" />
|
<UDashboardSidebarLinks :links="footerLinks" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<UDivider class="sticky bottom-0" />
|
<UDivider class="sticky bottom-0" />
|
||||||
<UserDropdown style="margin-bottom: env(safe-area-inset-bottom, 10px) !important;"/>
|
<UserDropdown style="margin-bottom: env(safe-area-inset-bottom, 10px) !important;"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -204,7 +204,8 @@ const setupPage = async () => {
|
|||||||
processDieselPosition()
|
processDieselPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (route.query.loadMode === "finalInvoice") {
|
}
|
||||||
|
else if (route.query.loadMode === "finalInvoice") {
|
||||||
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
let linkedDocuments = (await useEntities("createddocuments").select()).filter(i => JSON.parse(route.query.linkedDocuments).includes(i.id))
|
||||||
|
|
||||||
//TODO: Implement Checking for Same Customer, Contact and Project
|
//TODO: Implement Checking for Same Customer, Contact and Project
|
||||||
@@ -355,6 +356,8 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await setCustomerData(null, true)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +462,11 @@ const setCustomerData = async (customerId, loadOnlyAdress = false) => {
|
|||||||
|
|
||||||
let customer = customers.value.find(i => i.id === itemInfo.value.customer)
|
let customer = customers.value.find(i => i.id === itemInfo.value.customer)
|
||||||
console.log(customer)
|
console.log(customer)
|
||||||
itemInfo.value.contact = null
|
if(!loadOnlyAdress) {
|
||||||
|
itemInfo.value.contact = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (customer) {
|
if (customer) {
|
||||||
itemInfo.value.address.street = customer.infoData.street
|
itemInfo.value.address.street = customer.infoData.street
|
||||||
itemInfo.value.address.zip = customer.infoData.zip
|
itemInfo.value.address.zip = customer.infoData.zip
|
||||||
@@ -1824,7 +1831,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="contacts.filter(i => i.customer === itemInfo.customer)"
|
:options="contacts.filter(i => i.customer?.id === itemInfo.customer)"
|
||||||
option-attribute="fullName"
|
option-attribute="fullName"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
:search-attributes="['name']"
|
:search-attributes="['name']"
|
||||||
@@ -2057,7 +2064,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="plants.filter(i => i.customer === itemInfo.customer)"
|
:options="plants.filter(i => i.customer?.id === itemInfo.customer)"
|
||||||
v-model="itemInfo.plant"
|
v-model="itemInfo.plant"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
option-attribute="name"
|
option-attribute="name"
|
||||||
@@ -2095,7 +2102,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="projects.filter(i => i.customer === itemInfo.customer && (itemInfo.plant ? itemInfo.plant === i.plant : true))"
|
:options="projects.filter(i => i.customer?.id === itemInfo.customer && (itemInfo.plant ? itemInfo.plant === i.plant : true))"
|
||||||
v-model="itemInfo.project"
|
v-model="itemInfo.project"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
option-attribute="name"
|
option-attribute="name"
|
||||||
@@ -2134,7 +2141,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
>
|
>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
:options="contracts.filter(i => i.customer === itemInfo.customer && (itemInfo.plant ? itemInfo.plant === i.plant : true))"
|
:options="contracts.filter(i => i.customer?.id === itemInfo.customer && (itemInfo.plant ? itemInfo.plant === i.plant : true))"
|
||||||
v-model="itemInfo.contract"
|
v-model="itemInfo.contract"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
option-attribute="name"
|
option-attribute="name"
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const openBankstatements = () => {
|
|||||||
E-Mail
|
E-Mail
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton
|
<UButton
|
||||||
@click="router.push(`/createDocument/edit/?linkedDocument=${itemInfo.id}&loadMode=storno`)"
|
@click="router.push(`/createDocument/edit/?createddocument=${itemInfo.id}&loadMode=storno`)"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="rose"
|
color="rose"
|
||||||
v-if="itemInfo.type === 'invoices' || itemInfo.type === 'advanceInvoices'"
|
v-if="itemInfo.type === 'invoices' || itemInfo.type === 'advanceInvoices'"
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ const updateIncomingInvoice = async (setBooked = false) => {
|
|||||||
} else {
|
} else {
|
||||||
item.state = "Entwurf"
|
item.state = "Entwurf"
|
||||||
}
|
}
|
||||||
const data = await useEntities('incominginvoices').update(itemInfo.value.id,item)
|
const data = await useEntities('incominginvoices').update(itemInfo.value.id,item,true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const findIncomingInvoiceErrors = computed(() => {
|
const findIncomingInvoiceErrors = computed(() => {
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ async function loadWorkingTimeInfo() {
|
|||||||
|
|
||||||
// Erstellt Query-Parameter für den neuen Backend-Endpunkt
|
// Erstellt Query-Parameter für den neuen Backend-Endpunkt
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
from: selectedStartDay.value,
|
from: $dayjs(selectedStartDay.value).format("YYYY-MM-DD"),
|
||||||
to: selectedEndDay.value,
|
to: $dayjs(selectedEndDay.value).format("YYYY-MM-DD"),
|
||||||
targetUserId: evaluatedUserId.value,
|
targetUserId: evaluatedUserId.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,86 @@
|
|||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
const api = $fetch.create({
|
const api = $fetch.create({
|
||||||
baseURL: config.public.apiBase,
|
baseURL: config.public.apiBase,
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
async onRequest({options}) {
|
|
||||||
// Token aus Cookie holen
|
|
||||||
let token: string | null | undefined = ""
|
|
||||||
|
|
||||||
token = useCookie("token").value
|
async onRequest({ options }) {
|
||||||
|
const token = useCookie("token").value
|
||||||
|
|
||||||
|
// Falls im Request explizit ein anderer JWT übergeben wird
|
||||||
|
if (options.context?.jwt) {
|
||||||
// Falls im Request explizit ein anderer JWT übergeben wird → diesen verwenden
|
options.headers = {
|
||||||
if (options.context && (options.context as any).jwt) {
|
...options.headers,
|
||||||
token = (options.context as any).jwt
|
Authorization: `Bearer ${options.context.jwt}`,
|
||||||
}
|
}
|
||||||
|
} else if (token) {
|
||||||
if (token) {
|
|
||||||
options.headers = {
|
options.headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async onRequestError({error}) {
|
||||||
|
toast.add({
|
||||||
|
title: "Fehler",
|
||||||
|
description: "Eine Anfrage konnte nicht ausgeführt werden.",
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-heroicons-exclamation-triangle-20-solid',
|
||||||
|
timeout: 5000 // Bleibt 5 Sekunden sichtbar
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async onResponseError({ response }) {
|
||||||
|
// Toasts nur im Client anzeigen
|
||||||
|
console.log(response)
|
||||||
|
if (!process.client) return
|
||||||
|
|
||||||
|
const status = response.status
|
||||||
|
let title = "Fehler"
|
||||||
|
let description = "Ein unerwarteter Fehler ist aufgetreten."
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 400:
|
||||||
|
title = "Anfrage fehlerhaft"
|
||||||
|
description = "Die Daten konnten nicht korrekt verarbeitet werden."
|
||||||
|
break
|
||||||
|
case 401:
|
||||||
|
title = "Nicht angemeldet"
|
||||||
|
description = "Deine Sitzung ist abgelaufen oder ungültig."
|
||||||
|
// Optional: useCookie('token').value = null
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
title = "Zugriff verweigert"
|
||||||
|
description = "Du hast keine Berechtigung für diesen Bereich."
|
||||||
|
break
|
||||||
|
case 404:
|
||||||
|
title = "Nicht gefunden"
|
||||||
|
description = "Die gesuchte Ressource wurde nicht gefunden."
|
||||||
|
break
|
||||||
|
case 500:
|
||||||
|
title = "Server-Fehler"
|
||||||
|
description = "Internes Problem. Bitte versuche es später erneut."
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nuxt UI Toast Notification
|
||||||
|
toast.add({
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-heroicons-exclamation-triangle-20-solid',
|
||||||
|
timeout: 5000 // Bleibt 5 Sekunden sichtbar
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { provide: { api } }
|
return {
|
||||||
|
provide: {
|
||||||
|
api
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
@@ -2499,8 +2499,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
component: vehicle,
|
component: vehicle,
|
||||||
inputType: "select",
|
inputType: "select",
|
||||||
selectDataType: "vehicles",
|
selectDataType: "vehicles",
|
||||||
selectOptionAttribute: "licensePlate",
|
selectOptionAttribute: "license_plate",
|
||||||
selectSearchAttributes: ['licensePlate'],
|
selectSearchAttributes: ['license_plate','vin'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "inventoryitem",
|
key: "inventoryitem",
|
||||||
|
|||||||
Reference in New Issue
Block a user