Changed Plants to Objects

Changes in outgoinginvoices
This commit is contained in:
2024-01-27 11:54:14 +01:00
parent 6f6e835b0a
commit 3167b6a20a
21 changed files with 1821 additions and 23 deletions

11
demo.txt Normal file
View File

@@ -0,0 +1,11 @@
E-Mail: demo@spaces.software
Passwort: Pxj7fZFwAf
Login PW: F8FPPs3wQ7

View File

@@ -121,7 +121,7 @@ const navLinks = [
icon: "i-heroicons-clipboard-document" icon: "i-heroicons-clipboard-document"
}, },
{ {
label: "Anlagen", label: "Objekte",
to: "/plants", to: "/plants",
icon: "i-heroicons-clipboard-document" icon: "i-heroicons-clipboard-document"
}, },

View File

@@ -21,7 +21,6 @@ const openShowModal = ref(false)
//Functions //Functions
const openDocument = async () => { const openDocument = async () => {
console.log("open")
//selectedDocument.value = doc //selectedDocument.value = doc
openShowModal.value = true openShowModal.value = true
} }

View File

@@ -0,0 +1,141 @@
<script setup>
const editor = useEditor({
content: "<p>I'm running Tiptap with Vue.js. 🎉</p>",
extensions: [TiptapStarterKit],
});
</script>
<template>
<div>
<InputGroup>
<UButtonGroup>
<UButton
@click="editor.chain().focus().undo().run()"
:disabled="!editor.can().chain().focus().undo().run()"
icon="i-mdi-undo"
class="px-3"
/>
<UButton
@click="editor.chain().focus().redo().run()"
:disabled="!editor.can().chain().focus().redo().run()"
icon="i-mdi-redo"
class="px-3"
/>
</UButtonGroup>
<UButtonGroup v-if="editor">
<UButton
@click="editor.chain().focus().toggleBold().run()"
:disabled="!editor.can().chain().focus().toggleBold().run()"
:variant="editor.isActive('bold') ? 'solid' : 'outline'"
>
B
</UButton>
<UButton
@click="editor.chain().focus().toggleItalic().run()"
:disabled="!editor.can().chain().focus().toggleItalic().run()"
:variant="editor.isActive('italic') ? 'solid' : 'outline'"
>
<span class="italic">I</span>
</UButton>
<UButton
@click="editor.chain().focus().toggleStrike().run()"
:disabled="!editor.can().chain().focus().toggleStrike().run()"
:variant="editor.isActive('strike') ? 'solid' : 'outline'"
>
<span class="line-through">D</span>
</UButton>
</UButtonGroup>
<UButtonGroup>
<!-- <UButton
@click="editor.chain().focus().toggleCode().run()"
:disabled="!editor.can().chain().focus().toggleCode().run()"
:class="{ 'is-active': editor.isActive('code') }"
>
code
</UButton>
<UButton @click="editor.chain().focus().unsetAllMarks().run()">
clear marks
</UButton>
<UButton @click="editor.chain().focus().clearNodes().run()">
clear nodes
</UButton>
<UButton
@click="editor.chain().focus().setParagraph().run()"
:class="{ 'is-active': editor.isActive('paragraph') }"
>
<span>P</span>
</UButton>-->
<UButton
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
icon="i-mdi-format-header-1"
/>
<UButton
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
icon="i-mdi-format-header-2"
/>
<UButton
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
icon="i-mdi-format-header-3"
/>
<UButton
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"
icon="i-mdi-format-header-4"
/>
<UButton
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"
icon="i-mdi-format-header-5"
/>
<UButton
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"
icon="i-mdi-format-header-6"
/>
<UButton
@click="editor.chain().focus().toggleBulletList().run()"
:class="{ 'is-active': editor.isActive('bulletList') }"
icon="i-mdi-format-list-bulleted"
/>
<UButton
@click="editor.chain().focus().toggleOrderedList().run()"
:class="{ 'is-active': editor.isActive('orderedList') }"
icon="i-mdi-format-list-numbered"
/>
<!-- <UButton
@click="editor.chain().focus().toggleCodeBlock().run()"
:class="{ 'is-active': editor.isActive('codeBlock') }"
>
code block
</UButton>
<UButton
@click="editor.chain().focus().toggleBlockquote().run()"
:class="{ 'is-active': editor.isActive('blockquote') }"
>
blockquote
</UButton>
<UButton @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule
</UButton>
<UButton @click="editor.chain().focus().setHardBreak().run()">
hard break
</UButton>-->
</UButtonGroup>
</InputGroup>
<TiptapEditorContent class="mt-5" :editor="editor" />
</div>
</template>
<style scoped>
</style>

View File

@@ -46,7 +46,7 @@ const actions = [
}, },
{ {
id: 'new-plant', id: 'new-plant',
label: 'Anlage hinzufügen', label: 'Objekt hinzufügen',
icon: 'i-heroicons-clipboard-document', icon: 'i-heroicons-clipboard-document',
to: "/plants/create" , to: "/plants/create" ,
}, },
@@ -85,7 +85,7 @@ const groups = computed(() =>
commands: dataStore.tasks.map(item => { return {id: item.id, label: item.name, to: `/tasks/show/${item.id}`}}) commands: dataStore.tasks.map(item => { return {id: item.id, label: item.name, to: `/tasks/show/${item.id}`}})
},{ },{
key: "plants", key: "plants",
label: "Anlagen", label: "Objekte",
commands: dataStore.plants.map(item => { return {id: item.id, label: item.name, to: `/plants/show/${item.id}`}}) commands: dataStore.plants.map(item => { return {id: item.id, label: item.name, to: `/plants/show/${item.id}`}})
} }
].filter(Boolean)) ].filter(Boolean))

View File

@@ -70,6 +70,7 @@ const addHistoryItem = async () => {
if(error) { if(error) {
console.log(error) console.log(error)
} else { } else {
addHistoryItemData.value = {}
toast.add({title: "Eintrag erfolgreich erstellt"}) toast.add({title: "Eintrag erfolgreich erstellt"})
showAddHistoryItemModal.value = false showAddHistoryItemModal.value = false
await dataStore.fetchHistoryItems() await dataStore.fetchHistoryItems()

View File

@@ -0,0 +1,785 @@
import {PDFDocument, StandardFonts, rgb} from "pdf-lib"
let headerData = {
sender: "Federspiel Technology UG haftungsbeschränkt, Am Schwarzen Brack 14 26452 Sande",
recipient: {
name: "NOA Service GmbH",
contact: "Lena Kramer",
special: "Hinterm Haus",
address: "Oldenburger Str. 52",
city: "26340 Zetel"
},
info: {
invoiceNumber: "RE23-1409",
customerNumber: "10069",
invoiceDate: "15.09.2023",
dateOfPerformance: "31.08.2023",
contactPerson: "Florian Federspiel",
tel: "015755769509",
email: "f.federspiel@federspiel.tech"
},
title: "Rechnung-Nr. RE23-1409",
description: "BV: Stubbendränk 23, 26340 Zetel"
}
const getCoordinatesForPDFLib = (x ,y, page) => {
/*
* @param x the wanted X Parameter in Millimeters from Top Left
* @param y the wanted Y Parameter in Millimeters from Top Left
* @param page the page Object
*
* @returns x,y object
* */
let retX = x * 2.83
let retY = page.getHeight()-(y*2.83)
return {
x: retX,
y: retY
}
}
export const useCreatePdf = async (invoiceData) => {
const uri = ref("test")
const genPDF = async () => {
const pdfDoc = await PDFDocument.create()
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold)
let pages = []
let pageCounter = 1
const backgroundPdfSourceBuffer = await fetch("/Briefpapier.pdf").then((res) => res.arrayBuffer())
const backgroudPdf = await PDFDocument.load(backgroundPdfSourceBuffer)
const firstPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[0])
const secondPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[1])
//console.log("TEST")
const page1 = pdfDoc.addPage()
//console.log(page1.getSize().width/2.83)
page1.drawPage(firstPageBackground, {
x: 0,
y: 0,
})
//console.log(page1.getSize())
pages.push(page1)
//console.log(pages)
//Falzmarke 1
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(0,105,page1),
end: getCoordinatesForPDFLib(7,105,page1),
thickness: 0.25,
color: rgb(0,0,0),
opacity: 1
})
//Lochmarke
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(0,148.5,page1),
end: getCoordinatesForPDFLib(7,148.5,page1),
thickness: 0.25,
color: rgb(0,0,0),
opacity: 1
})
//Falzmarke 2
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(0,210,page1),
end: getCoordinatesForPDFLib(7,210,page1),
thickness: 0.25,
color: rgb(0,0,0),
opacity: 1
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,45,page1),
end: getCoordinatesForPDFLib(105,45,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
pages[pageCounter - 1].drawText("Federspiel Technology UG haftungsbeschränkt, Am Schwarzen Brack 14 26452 Sande", {
...getCoordinatesForPDFLib(21,48, page1),
size:6,
color:rgb(0,0,0),
lineHeight:6,
opacity: 1,
maxWidth: 240
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,50,page1),
end: getCoordinatesForPDFLib(105,50,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
pages[pageCounter - 1].drawText(invoiceData.recipient.name, {
...getCoordinatesForPDFLib(21,55, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.recipient.contact, {
...getCoordinatesForPDFLib(21,60, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.recipient.special, {
...getCoordinatesForPDFLib(21,65, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.recipient.street, {
...getCoordinatesForPDFLib(21,70, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(`${invoiceData.recipient.zip} ${invoiceData.recipient.city}`, {
...getCoordinatesForPDFLib(21,75, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,90,page1),
end: getCoordinatesForPDFLib(105,90,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
//Rechts
/*page1.drawLine({
start: getCoordinatesForPDFLib(125,50,page1),
end: getCoordinatesForPDFLib(200,50,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
pages[pageCounter - 1].drawText("Rechnungsnummer", {
...getCoordinatesForPDFLib(126,55, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.documentNumber, {
y: getCoordinatesForPDFLib(126,55, page1).y,
x: getCoordinatesForPDFLib(126,55,page1).x + 210 - font.widthOfTextAtSize(headerData.info.invoiceNumber,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText("Kundennummer", {
...getCoordinatesForPDFLib(126,60, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.customerNumber, {
y: getCoordinatesForPDFLib(126,60, page1).y,
x: getCoordinatesForPDFLib(126,60,page1).x + 210 - font.widthOfTextAtSize(headerData.info.customerNumber,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText("Belegdatum", {
...getCoordinatesForPDFLib(126,65, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.documentDate, {
y: getCoordinatesForPDFLib(126,65, page1).y,
x: getCoordinatesForPDFLib(126,65,page1).x + 210 - font.widthOfTextAtSize(headerData.info.invoiceDate,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText("Lieferdatum", {
...getCoordinatesForPDFLib(126,70, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.deliveryDate, {
y: getCoordinatesForPDFLib(126,70, page1).y,
x: getCoordinatesForPDFLib(126,70,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.deliveryDate,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText("Ansprechpartner", {
...getCoordinatesForPDFLib(126,75, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.contactPerson, {
y: getCoordinatesForPDFLib(126,75, page1).y,
x: getCoordinatesForPDFLib(126,75,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.contactPerson,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText("Telefon", {
...getCoordinatesForPDFLib(126,80, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.contactTel, {
y: getCoordinatesForPDFLib(126,80, page1).y,
x: getCoordinatesForPDFLib(126,80,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.contactTel,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText("E-Mail", {
...getCoordinatesForPDFLib(126,85, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.contactEMail, {
y: getCoordinatesForPDFLib(126,85, page1).y,
x: getCoordinatesForPDFLib(126,85,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.contactEMail,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText("Projekt:", {
...getCoordinatesForPDFLib(126,90, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.info.project, {
y: getCoordinatesForPDFLib(126,90, page1).y,
x: getCoordinatesForPDFLib(126,90,page1).x + 340 - font.widthOfTextAtSize(invoiceData.info.project,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 100
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(125,90,page1),
end: getCoordinatesForPDFLib(200,90,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
//Title
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,95,page1),
end: getCoordinatesForPDFLib(200,95,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
pages[pageCounter - 1].drawText(invoiceData.title, {
...getCoordinatesForPDFLib(20,102, page1),
size:15,
color:rgb(0,0,0),
lineHeight:15,
opacity: 1,
maxWidth: 240
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,105,page1),
end: getCoordinatesForPDFLib(200,105,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
pages[pageCounter - 1].drawText(invoiceData.description, {
...getCoordinatesForPDFLib(20,112, page1),
size:15,
color:rgb(0,0,0),
lineHeight:15,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(invoiceData.startText,{
...getCoordinatesForPDFLib(20,119, page1),
size: 10,
color: rgb(0,0,0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,115,page1),
end: getCoordinatesForPDFLib(200,115,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
pages[pageCounter - 1].drawRectangle({
...getCoordinatesForPDFLib(20,140, page1),
width: 180 * 2.83,
height: 8 * 2.83,
color: rgb(0,0,0),
opacity: 0.25
})
//Header
pages[pageCounter - 1].drawText("Pos", {
...getCoordinatesForPDFLib(21,137, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Menge", {
...getCoordinatesForPDFLib(35,137, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Bezeichnung", {
...getCoordinatesForPDFLib(52,137, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Einheitspreis", {
...getCoordinatesForPDFLib(135,137, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Gesamt", {
y: getCoordinatesForPDFLib(25,137, page1).y,
x: getCoordinatesForPDFLib(25,137,page1).x + 490 - fontBold.widthOfTextAtSize("Gesamt",12),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
let rowHeight = 145
let pageIndex = 0
invoiceData.rows.forEach((row,index) => {
if(row.mode === 'free' || row.mode === 'normal'){
pages[pageCounter - 1].drawText(row.pos, {
...getCoordinatesForPDFLib(21,rowHeight, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(`${row.quantity} ${row.unit}`, {
...getCoordinatesForPDFLib(35,rowHeight, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(row.text, {
...getCoordinatesForPDFLib(52,rowHeight, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240,
font: fontBold
})
if(row.description) {
pages[pageCounter - 1].drawText(row.description, {
...getCoordinatesForPDFLib(52,rowHeight + 7, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 220,
})
}
pages[pageCounter - 1].drawText(row.price, {
...getCoordinatesForPDFLib(135,rowHeight, page1),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(row.rowAmount, {
y: getCoordinatesForPDFLib(25,rowHeight, page1).y,
x: getCoordinatesForPDFLib(25,rowHeight,page1).x + 490 - font.widthOfTextAtSize(row.rowAmount,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240,
})
if(row.discountPercent > 0) {
pages[pageCounter - 1].drawText(row.discountText, {
y: getCoordinatesForPDFLib(25,rowHeight + 5, page1).y,
x: getCoordinatesForPDFLib(25,rowHeight + 5,page1).x + 490 - font.widthOfTextAtSize(row.discountText,8),
size:8,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240,
})
}
rowHeight += 14
pageIndex += 1
} else if(row.mode === 'pagebreak') {
const page = pdfDoc.addPage()
page.drawPage(secondPageBackground, {
x: 0,
y: 0,
})
//Falzmarke 1
page.drawLine({
start: getCoordinatesForPDFLib(0,105,page1),
end: getCoordinatesForPDFLib(7,105,page1),
thickness: 0.25,
color: rgb(0,0,0),
opacity: 1
})
//Lochmarke
page.drawLine({
start: getCoordinatesForPDFLib(0,148.5,page1),
end: getCoordinatesForPDFLib(7,148.5,page1),
thickness: 0.25,
color: rgb(0,0,0),
opacity: 1
})
//Falzmarke 2
page.drawLine({
start: getCoordinatesForPDFLib(0,210,page1),
end: getCoordinatesForPDFLib(7,210,page1),
thickness: 0.25,
color: rgb(0,0,0),
opacity: 1
})
page.drawRectangle({
...getCoordinatesForPDFLib(20,25, page1),
width: 180 * 2.83,
height: 8 * 2.83,
color: rgb(0,0,0),
opacity: 0.25
})
//Header
page.drawText("Pos", {
...getCoordinatesForPDFLib(21,22, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Menge", {
...getCoordinatesForPDFLib(35,22, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Bezeichnung", {
...getCoordinatesForPDFLib(52,22, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Einheitspreis", {
...getCoordinatesForPDFLib(135,22, page1),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Gesamt", {
y: getCoordinatesForPDFLib(25,22, page1).y,
x: getCoordinatesForPDFLib(25,22,page1).x + 490 - fontBold.widthOfTextAtSize("Gesamt",12),
size:12,
color:rgb(0,0,0),
lineHeight:12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pageCounter += 1;
pageIndex = 0;
rowHeight = 30;
pages.push(page)
//console.log(pages)
}
})
//Pos 1
//Footer
rowHeight += 25
pages[pageCounter - 1].drawRectangle({
...getCoordinatesForPDFLib(20,rowHeight, page1),
width: 180 * 2.83,
height: 8 * 2.83,
color: rgb(0,0,0),
opacity: 0.25
})
pages[pageCounter - 1].drawRectangle({
...getCoordinatesForPDFLib(20,rowHeight +16, page1),
width: 180 * 2.83,
height: 8 * 2.83,
color: rgb(0,0,0),
opacity: 0.25
})
pages[pageCounter - 1].drawText("Nettobetrag", {
...getCoordinatesForPDFLib(21,rowHeight-3, page1),
size:11,
color:rgb(0,0,0),
lineHeight:11,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText(invoiceData.total.totalNet, {
y: getCoordinatesForPDFLib(21,rowHeight-3, page1).y,
x: getCoordinatesForPDFLib(21,rowHeight-3,page1).x + 500 - fontBold.widthOfTextAtSize(invoiceData.total.totalNet,11),
size:11,
color:rgb(0,0,0),
lineHeight:11,
opacity: 1,
maxWidth: 240,
font:fontBold
})
pages[pageCounter - 1].drawText("zzgl. 19% MwSt", {
...getCoordinatesForPDFLib(21,rowHeight+5, page1),
size:11,
color:rgb(0,0,0),
lineHeight:11,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText(invoiceData.total.total19, {
y: getCoordinatesForPDFLib(21,rowHeight+5, page1).y,
x: getCoordinatesForPDFLib(21,rowHeight+5,page1).x + 500 - fontBold.widthOfTextAtSize(invoiceData.total.total19,11),
size:11,
color:rgb(0,0,0),
lineHeight:11,
opacity: 1,
maxWidth: 240,
font:fontBold
})
pages[pageCounter - 1].drawText("Gesamtsumme", {
...getCoordinatesForPDFLib(21,rowHeight+13, page1),
size:11,
color:rgb(0,0,0),
lineHeight:11,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText(invoiceData.total.totalGross, {
y: getCoordinatesForPDFLib(21,rowHeight+13, page1).y,
x: getCoordinatesForPDFLib(21,rowHeight+13,page1).x + 500 - fontBold.widthOfTextAtSize(invoiceData.total.totalGross,11),
size:11,
color:rgb(0,0,0),
lineHeight:11,
opacity: 1,
maxWidth: 240,
font:fontBold
})
pages[pageCounter - 1].drawText(invoiceData.endText,{
...getCoordinatesForPDFLib(20,rowHeight+22, page1),
size: 10,
color: rgb(0,0,0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
})
//console.log(await pdfDoc.saveAsBase64({dataUri: true}))
uri.value = await pdfDoc.saveAsBase64({dataUri: true})
//console.log(uri.value)
}
await genPDF()
//const pdfBytes = await pdfDoc.save()
//const pdfDataUri = await pdfDoc.saveAsBase64({dataUri: true})
return uri.value
//let blob = new Blob(pdfBytes, {type: "application/pdf"})
/*let link = document.createElement('a')
link.href = pdfDataUri//window.URL.createObjectURL(blob)
link.download = "test.pdf"
link.click()*/
}

View File

@@ -19,7 +19,8 @@ export default defineNuxtConfig({
'@nuxtjs/fontaine', '@nuxtjs/fontaine',
'@nuxtjs/google-fonts', '@nuxtjs/google-fonts',
'@vite-pwa/nuxt', '@vite-pwa/nuxt',
'nuxt-viewport' 'nuxt-viewport',
'nuxt-tiptap-editor'
], ],
routeRules: { routeRules: {
@@ -36,7 +37,7 @@ export default defineNuxtConfig({
}, },
}, },
ui: { ui: {
icons: ['heroicons'] icons: ['heroicons','mdi']
}, },
colorMode: { colorMode: {
preference: 'dark' preference: 'dark'
@@ -60,6 +61,9 @@ export default defineNuxtConfig({
enabled: false, enabled: false,
type: "module" type: "module"
} }
},
tiptap: {
prefix: "Tiptap"
} }

View File

@@ -14,6 +14,7 @@
"@nuxtjs/supabase": "^1.1.4", "@nuxtjs/supabase": "^1.1.4",
"@vite-pwa/nuxt": "^0.3.3", "@vite-pwa/nuxt": "^0.3.3",
"nuxt": "^3.8.0", "nuxt": "^3.8.0",
"nuxt-tiptap-editor": "^0.0.13",
"vite-plugin-pwa": "^0.17.3", "vite-plugin-pwa": "^0.17.3",
"vue": "^3.3.7", "vue": "^3.3.7",
"vue-router": "^4.2.5" "vue-router": "^4.2.5"
@@ -27,6 +28,7 @@
"@fullcalendar/resource-timeline": "^6.1.10", "@fullcalendar/resource-timeline": "^6.1.10",
"@fullcalendar/timegrid": "^6.1.10", "@fullcalendar/timegrid": "^6.1.10",
"@fullcalendar/vue3": "^6.1.10", "@fullcalendar/vue3": "^6.1.10",
"@iconify/json": "^2.2.171",
"@nuxt/content": "^2.9.0", "@nuxt/content": "^2.9.0",
"@nuxt/ui-pro": "^0.7.0", "@nuxt/ui-pro": "^0.7.0",
"@nuxtjs/fontaine": "^0.4.1", "@nuxtjs/fontaine": "^0.4.1",
@@ -49,6 +51,7 @@
"nuxt-editorjs": "^1.0.4", "nuxt-editorjs": "^1.0.4",
"nuxt-viewport": "^2.0.6", "nuxt-viewport": "^2.0.6",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"pdf-lib": "^1.17.1",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"sass": "^1.69.7", "sass": "^1.69.7",
"socket.io-client": "^4.7.2", "socket.io-client": "^4.7.2",

View File

@@ -44,7 +44,8 @@ const itemInfo = ref({
end: "", end: "",
notes: null, notes: null,
projectId: null, projectId: null,
type: null type: null,
state: "Entwurf"
}) })
@@ -300,9 +301,10 @@ const setState = async (newState) => {
:dark="useColorMode().value !== 'light'" :dark="useColorMode().value !== 'light'"
:format="format" :format="format"
:preview-format="format" :preview-format="format"
:disabled="itemInfo.state !== 'Entwurf'" :disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
/> />
</UFormGroup> </UFormGroup>
{{itemInfo.state === 'Entwurf'}}{{itemInfo.state}}
<UFormGroup <UFormGroup
label="Ende:" label="Ende:"
> >
@@ -316,7 +318,7 @@ const setState = async (newState) => {
:dark="useColorMode().value !== 'light'" :dark="useColorMode().value !== 'light'"
:format="format" :format="format"
:preview-format="format" :preview-format="format"
:disabled="itemInfo.state !== 'Entwurf'" :disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
/> />
</UFormGroup> </UFormGroup>
<!-- <UFormGroup <!-- <UFormGroup
@@ -334,7 +336,7 @@ const setState = async (newState) => {
v-model="itemInfo.user" v-model="itemInfo.user"
option-attribute="fullName" option-attribute="fullName"
value-attribute="id" value-attribute="id"
:disabled="itemInfo.state !== 'Entwurf'" :disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
> >
<template #label> <template #label>
{{dataStore.profiles.find(profile => profile.id === itemInfo.user) ? dataStore.profiles.find(profile => profile.id === itemInfo.user).firstName : "Benutzer auswählen"}} {{dataStore.profiles.find(profile => profile.id === itemInfo.user) ? dataStore.profiles.find(profile => profile.id === itemInfo.user).firstName : "Benutzer auswählen"}}
@@ -352,7 +354,7 @@ const setState = async (newState) => {
searchable searchable
searchable-placeholder="Suche..." searchable-placeholder="Suche..."
:search-attributes="['name']" :search-attributes="['name']"
:disabled="itemInfo.state !== 'Entwurf'" :disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
> >
<template #label> <template #label>
{{dataStore.projects.find(project => project.id === itemInfo.projectId) ? dataStore.projects.find(project => project.id === itemInfo.projectId).name : "Projekt auswählen"}} {{dataStore.projects.find(project => project.id === itemInfo.projectId) ? dataStore.projects.find(project => project.id === itemInfo.projectId).name : "Projekt auswählen"}}
@@ -367,7 +369,7 @@ const setState = async (newState) => {
:options="timeTypes" :options="timeTypes"
option-attribute="label" option-attribute="label"
value-attribute="label" value-attribute="label"
:disabled="itemInfo.state !== 'Entwurf'" :disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
> >
<template #label> <template #label>
{{itemInfo.type ? itemInfo.type : "Kategorie auswählen"}} {{itemInfo.type ? itemInfo.type : "Kategorie auswählen"}}
@@ -379,7 +381,7 @@ const setState = async (newState) => {
> >
<UTextarea <UTextarea
v-model="itemInfo.notes" v-model="itemInfo.notes"
:disabled="itemInfo.state !== 'Entwurf'" :disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
/> />
</UFormGroup> </UFormGroup>

View File

@@ -0,0 +1,835 @@
<script setup>
import dayjs from "dayjs"
const dataStore = useDataStore()
const user = useSupabaseUser()
const tabItems = [
{
label: "Editor"
},
{
label: "Vorschau"
}
]
const itemInfo = ref({
customer: 13,
contact: 8,
address: {
street: null,
special: null,
zip: null,
city: null,
},
project: 6,
documentNumber: "RE23-1409",
documentDate: "10.01.2024",
deliveryDate: "10.01.2024",
dateOfPerformance: null,
createdBy: user.value.id,
title: "TITEL",
description: "BESCHREIBUNG",
startText: "Sehr geehrte Frau Sindern,\n" +
"wir bedanken uns für Ihr entgegengebrachtes Vertrauen und Ihren Auftrag und stellen Ihnen\n" +
"folgende Positionen in Rechnung: ",
endText: "Bitte überweisen Sie den Rechnungsbetrag unter Angabe der Rechnungsnummer im Verwendungszweck innerhalb von 10 Tagen auf das unten angegebene Konto. Wir bedanken uns für das entgegengebrachte Vertrauen und freuen uns auf eine weitere gute Zusammenarbeit.",
rows: [
{
id: 1,
pos: 1,
mode: "free",
text: "FahrtkostenFahrtkostenFahrtkostenFahrtkosten",
quantity: 204,
unit: 3,
price: 0.80,
taxPercent: 19,
discountPercent: 0
},
{
id: 2,
pos: 2,
mode: "free",
text: "Test",
quantity: 1,
unit: 1,
price: 10,
taxPercent: 19,
discountPercent: 0
}
]
})
const getRowAmount = (row) => {
return String(Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(2)).replace('.',',')
}
const addPosition = (mode) => {
let lastId = 0
itemInfo.value.rows.forEach(row => {
if(row.id > lastId) lastId = row.id
})
if(mode === 'free'){
itemInfo.value.rows.push({
id: lastId +1,
mode: "free",
text: "",
quantity: 1,
unit: 1,
price: 0,
taxPercent: 19,
discountPercent: 0
})
} else if(mode === 'normal'){
itemInfo.value.rows.push({
id: lastId +1,
mode: "normal",
quantity: 1,
price: 0,
taxPercent: 19,
discountPercent: 0
})
} else if(mode === "pagebreak") {
itemInfo.value.rows.push({
id: lastId +1,
mode: "pagebreak",
})
}
setPosNumbers()
}
const removePosition = (id) => {
let rows = itemInfo.value.rows.filter(row => row.id !== id)
/*rows = rows.sort((a,b) => a.pos - b.pos)
rows.forEach((row,index) => {
rows[index] = {...row, pos: index + 1}
})*/
itemInfo.value.rows = rows
setPosNumbers()
}
const documentTotal = computed(() => {
let totalNet = 0
let total19 = 0
let totalGross = 0
itemInfo.value.rows.forEach(row => {
if(row.mode === 'free' || row.mode === 'normal'){
let rowPrice = Number(Number(row.quantity) * Number(row.price)).toFixed(2)
totalNet += Number(rowPrice)
if(row.taxPercent === 19) {
total19 += Number(rowPrice * 0.19)
}
}
})
return {
totalNet: `${String(totalNet.toFixed(2)).replace(".",",")}`,
total19: `${String(total19.toFixed(2)).replace(".",",")}`,
totalGross: `${String(Number(totalNet + total19).toFixed(2)).replace(".",",")}`
}
})
const getDocumentData = () => {
let customerData = dataStore.getCustomerById(itemInfo.value.customer)
let contactData = dataStore.getContactById(itemInfo.value.contact)
let userData = dataStore.getProfileById(user.value.id)
let rows = itemInfo.value.rows.map(row => {
let unit = dataStore.units.find(i => i.id === row.unit)
if(row.mode === 'free' || row.mode === 'normal') {
if(row.mode === 'normal') row.text = dataStore.getProductById(row.product).name
return {
...row,
rowAmount: `${getRowAmount(row)}`,
quantity: String(row.quantity).replace(".",","),
unit: unit.short,
pos: String(row.pos),
price: `${String(row.price.toFixed(2)).replace(".",",")}`,
discountText: `(Rabatt: ${row.discountPercent} %)`
}
} else {
return row
}
})
const returnData = {
recipient: {
name: customerData.name,
contact: `${contactData.firstName} ${contactData.lastName}`,
street: customerData.infoData.street,
special: "",
city: customerData.infoData.city,
zip: customerData.infoData.zip
},
info: {
customerNumber: customerData.customerNumber,
documentNumber: itemInfo.value.documentNumber,
documentDate: dayjs(itemInfo.value.documentDate, 'DD.MM.YYYY').format("DD.MM.YYYY"),
deliveryDate: dayjs(itemInfo.value.deliveryDate, 'DD.MM.YYYY').format("DD.MM.YYYY"),
contactPerson: userData.fullName ||"",
contactTel: userData.mobileTel ||"",
contactEMail: userData.email,
project: dataStore.getProjectById(itemInfo.value.project).name
},
title: itemInfo.value.title,
description: itemInfo.value.description,
endText: itemInfo.value.endText,
startText: itemInfo.value.startText,
rows: rows,
total: documentTotal.value
}
console.log(returnData)
return returnData
}
const showDocument = ref(false)
const uri = ref("")
const generateDocument = async () => {
uri.value = await useCreatePdf(getDocumentData())
//alert(uri.value)
showDocument.value = true
}
const onChangeTab = (index) => {
if(index === 1) {
generateDocument()
}
}
const setPosNumbers = () => {
let index = 1
let rows = itemInfo.value.rows.map(row => {
if(row.mode === 'free' ||row.mode === 'normal') {
row.pos = index
index += 1
}
return row
})
}
</script>
<template>
<!-- <UButton
@click="createPdf"
>TEST</UButton>-->
<UCard class="h-fit">
<UTabs :items="tabItems" @change="onChangeTab">
<template #item="{item}">
<div v-if="item.label === 'Editor'">
<InputGroup>
<div class="flex-auto mr-5">
<UFormGroup
label="Kunde:"
>
<USelectMenu
:options="dataStore.customers"
option-attribute="name"
value-attribute="id"
:search-attributes="['name']"
searchable
searchable-placeholder="Suche..."
v-model="itemInfo.customer"
>
<template #label>
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kein Kunde ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Ansprechpartner:"
>
<USelectMenu
:options="dataStore.getContactsByCustomerId(itemInfo.customer)"
option-attribute="fullName"
value-attribute="id"
:search-attributes="['name']"
searchable
searchable-placeholder="Suche..."
v-model="itemInfo.contact"
>
<template #label>
{{dataStore.getContactById(itemInfo.contact) ? dataStore.getContactById(itemInfo.contact).fullName : "Kein Kontakt ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Adresse:"
>
<UInput
v-model="itemInfo.address.street"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.street : 'Straße + Hausnummer'"
/>
<UInput
v-model="itemInfo.address.special"
class="mt-3"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.special : 'Adresszusatz'"
/>
<InputGroup class="mt-3">
<UInput
class="flex-auto"
v-model="itemInfo.address.zip"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.zip : 'PLZ'"
/>
<UInput
class="flex-auto"
v-model="itemInfo.address.city"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.city : 'Ort'"
/>
</InputGroup>
</UFormGroup>
</div>
<div class="flex-auto">
<UFormGroup
label="Rechnungsnummer:"
>
<UInput
v-model="itemInfo.documentNumber"
/>
</UFormGroup>
<UFormGroup
label="Datum:"
>
<UInput
v-model="itemInfo.documentDate"
/>
</UFormGroup>
<UFormGroup
label="Lieferdatum:"
>
<UInput
v-model="itemInfo.deliveryDate"
/>
</UFormGroup>
<UFormGroup
label="Ansprechpartner:"
>
<UInput
/>
</UFormGroup>
<UFormGroup
label="Kontakt Telefon:"
>
<UInput
/>
</UFormGroup>
<UFormGroup
label="Kontakt E-Mail:"
>
<UInput
/>
</UFormGroup><UFormGroup
label="Kontakt E-Mail:"
>
<UInput
/>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<USelectMenu
:options="dataStore.projects"
v-model="itemInfo.project"
value-attribute="id"
option-attribute="name"
searchable
searchable-placeholder="Suche..."
:search-attributes="['name']"
>
<template #label>
{{dataStore.getProjectById(itemInfo.project) ? dataStore.getProjectById(itemInfo.project).name : "Kein Projekt ausgewählt"}}
</template>
<template #option="{option: project}">
{{dataStore.getCustomerById(project.customer).name}} - {{project.name}}
</template>
</USelectMenu>
</UFormGroup>
</div>
</InputGroup>
<UDivider
class="my-3"
/>
<UFormGroup
label="Titel:"
>
<UInput v-model="itemInfo.title"/>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
class="mt-3"
>
<UInput v-model="itemInfo.description"/>
</UFormGroup>
<UDivider
class="my-3"
/>
<UFormGroup
label="Einleitung:"
>
<UTextarea
v-model="itemInfo.startText"
/>
</UFormGroup>
<UDivider
class="my-3"
/>
<table class="w-full">
<thead>
<tr>
<th></th>
<th>Pos.</th>
<th>Produkt / Leistung</th>
<th>Menge</th>
<th>Einheit</th>
<th>Preis</th>
<th>Steuer</th>
<th>Rabatt</th>
<th>Gesamt</th>
</tr>
</thead>
<draggable
v-model="itemInfo.rows"
handle=".handle"
tag="tbody"
itemKey="pos"
@end="setPosNumbers"
>
<template #item="{element: row}">
<tr
>
<td>
<UIcon
class="handle"
name="i-mdi-menu"
/>
</td>
<td
v-if="row.mode === 'pagebreak'"
colspan="8"
>
<UDivider/>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>{{row.pos}}</td>
<td
class="w-120"
v-if="row.mode === 'free'"
>
<UInput
v-model="row.text"
maxlength="40"
placeholder="Name"
/>
</td>
<td
class="w-120"
v-else-if="row.mode === 'normal'"
>
<USelectMenu
:options="dataStore.products"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche ..."
:search-attributes="['name']"
v-model="row.product"
@change="row.unit = dataStore.getProductById(row.product).unit,
row.price = dataStore.getProductById(row.product).sellingPrice || 0"
>
<template #label>
{{dataStore.getProductById(row.product) ?dataStore.getProductById(row.product).name : "Kein Produkt ausgewählt" }}
</template>
</USelectMenu>
</td>
<td
class="w-20"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.quantity"
type="number"
:step="dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).step : '1' "
/>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
v-model="row.unit"
:options="dataStore.units"
option-attribute="name"
value-attribute="id"
>
<template #label>
{{dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).name : "Keine Einheit gewählt"}}
</template>
</USelectMenu>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.price"
type="number"
step="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
:options="['19','7','0']"
v-model="row.taxPercent"
>
<template #option="{option}">
{{option}} %
</template>
<template #label>
{{row.taxPercent}} %
</template>
</USelectMenu>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.discountPercent"
type="number"
step="0.01"
placeholder="0"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template>
</UInput>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<div class="text-right font-bold">{{getRowAmount(row)}} </div>
</td>
<td>
<UButton
variant="ghost"
color="rose"
icon="i-heroicons-x-mark-16-solid"
@click="removePosition(row.id)"
/>
</td>
</tr>
</template>
</draggable>
<!-- <template v-for="row in itemInfo.rows">
<tr
>
<td
v-if="row.mode === 'pagebreak'"
colspan="8"
>
<UDivider/>
</td>
<td
rowspan="2"
v-if="row.mode === 'free' || row.mode === 'normal'"
>{{row.pos}}</td>
<td
class="w-120"
v-if="row.mode === 'free'"
>
<UInput
v-model="row.text"
maxlength="40"
placeholder="Name"
/>
</td>
<td
class="w-120"
v-else-if="row.mode === 'normal'"
>
<USelectMenu
:options="dataStore.products"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche ..."
:search-attributes="['name']"
v-model="row.product"
@change="row.unit = dataStore.getProductById(row.product).unit,
row.price = dataStore.getProductById(row.product).sellingPrice || 0"
>
<template #label>
{{dataStore.getProductById(row.product) ?dataStore.getProductById(row.product).name : "Kein Produkt ausgewählt" }}
</template>
</USelectMenu>
</td>
<td
class="w-20"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.quantity"
type="number"
:step="dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).step : '1' "
/>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
v-model="row.unit"
:options="dataStore.units"
option-attribute="name"
value-attribute="id"
>
<template #label>
{{dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).name : "Keine Einheit gewählt"}}
</template>
</USelectMenu>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.price"
type="number"
step="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
:options="['19','7','0']"
v-model="row.taxPercent"
>
<template #option="{option}">
{{option}} %
</template>
<template #label>
{{row.taxPercent}} %
</template>
</USelectMenu>
&lt;!&ndash; <UInput
v-model="row.taxPercent"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template>
</UInput>&ndash;&gt;
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.discountPercent"
type="number"
step="0.01"
placeholder="0"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template>
</UInput>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
rowspan="2"
>
<div class="text-right font-bold">{{getRowAmount(row)}} </div>
</td>
<td rowspan="2">
<UButton
variant="ghost"
color="rose"
icon="i-heroicons-x-mark-16-solid"
@click="removePosition(row.pos)"
/>
</td>
</tr>
<tr>
<td colspan="6">
<UTextarea
v-model="row.description"
placeholder="Beschreibung"
/>
</td>
</tr>
</template>-->
</table>
<table>
<tr>
<td class="font-bold">Netto:</td>
<td>{{documentTotal.totalNet}}</td>
</tr>
<tr>
<td class="font-bold">zzgl. 19 % USt:</td>
<td>{{documentTotal.total19}}</td>
</tr>
<tr>
<td class="font-bold">Brutto:</td>
<td>{{documentTotal.totalGross}}</td>
</tr>
</table>
<InputGroup>
<UButton
@click="addPosition('normal')"
class="mt-3"
>
+ Artikelposition
</UButton>
<UButton
@click="addPosition('free')"
class="mt-3"
>
+ Freitextposition
</UButton>
<UButton
@click="addPosition('pagebreak')"
class="mt-3"
>
+ Seitenumbruch
</UButton>
</InputGroup>
<UDivider
class="my-3"
/>
<UFormGroup
label="Nachbemerkung:"
>
<UTextarea
v-model="itemInfo.endText"
/>
</UFormGroup>
</div>
<div v-else-if="item.label === 'Vorschau'">
<!-- <UButton
@click="generateDocument"
>
Show
</UButton>-->
<object
:data="uri"
v-if="showDocument"
type="application/pdf"
class="w-full previewDocument"
/>
</div>
</template>
</UTabs>
</UCard>
</template>
<style scoped>
th {
text-align: left;
}
td {
padding: .4em;
}
/*tr:hover {
border: 1px solid #69c350;
}*/
.previewDocument {
height: 80vh;
}
</style>

View File

@@ -12,6 +12,11 @@ const router = useRouter()
const toast = useToast() const toast = useToast()
const id = ref(route.params.id ? route.params.id : null ) const id = ref(route.params.id ? route.params.id : null )
const editor = useEditor({
content: "<p>I'm running Tiptap with Vue.js. 🎉</p>",
extensions: [TiptapStarterKit],
});
let currentItem = null let currentItem = null
//Working //Working
@@ -25,6 +30,8 @@ const tabItems = [
label: "Projekte" label: "Projekte"
},{ },{
label: "Aufgaben" label: "Aufgaben"
},{
label: "Dokumentation"
} }
] ]
@@ -93,6 +100,11 @@ setupPage()
</UTable> </UTable>
</div> </div>
<div v-if="item.label === 'Dokumentation'">
<Editor/>
</div>
</template> </template>
</UTabs> </UTabs>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div id="main"> <div id="main">
<InputGroup> <InputGroup>
<UButton @click="router.push(`/plants/create/`)">+ Anlage</UButton> <UButton @click="router.push(`/plants/create/`)">+ Objekt</UButton>
<UInput <UInput
v-model="searchString" v-model="searchString"

View File

@@ -479,7 +479,7 @@ setupPage()
</UFormGroup> </UFormGroup>
<UFormGroup <UFormGroup
label="Anlage:" label="Objekt:"
> >
<USelectMenu <USelectMenu
v-model="itemInfo.plant" v-model="itemInfo.plant"
@@ -490,7 +490,7 @@ setupPage()
:search-attributes="['name']" :search-attributes="['name']"
> >
<template #label> <template #label>
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Anlage auswählen"}} {{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Objekt auswählen"}}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormGroup>

View File

@@ -54,7 +54,7 @@ const itemColumns = [
}, },
{ {
key: "plant", key: "plant",
label: "Anlage", label: "Objekt",
sortable: true sortable: true
} }
] ]

View File

@@ -55,7 +55,7 @@ const itemColumns = [
const selectItem = (item) => { const selectItem = (item) => {
console.log(item) console.log(item)
router.push(`/inventory/spaces/show/${item.id} `) router.push(`/spaces/show/${item.id} `)
} }

View File

@@ -162,7 +162,7 @@ setupPage()
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormGroup>
<UFormGroup <UFormGroup
label="Anlage:" label="Objekt:"
> >
<USelectMenu <USelectMenu
v-model="itemInfo.plant" v-model="itemInfo.plant"
@@ -174,7 +174,7 @@ setupPage()
:search-attributes="['name']" :search-attributes="['name']"
> >
<template #label> <template #label>
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Keine Anlage ausgewählt"}} {{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Kein Objekt ausgewählt"}}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormGroup>

View File

@@ -76,7 +76,7 @@ const columns = [
sortable: true sortable: true
},{ },{
key: "plant", key: "plant",
label: "Anlage:", label: "Objekt:",
sortable: true sortable: true
} }
] ]

View File

@@ -0,0 +1,5 @@
import draggable from 'vuedraggable'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component('draggable', draggable);
})

Binary file not shown.

View File

@@ -39,8 +39,8 @@ export const useDataStore = defineStore('data', () => {
redirect:true redirect:true
}, },
plants: { plants: {
label: "Anlagen", label: "Objekte",
labelSingle: "Anlage", labelSingle: "Objekte",
redirect:true redirect:true
}, },
products: { products: {