+
{{ title }}
@@ -23,6 +23,7 @@ const emit = defineEmits<{ close: [] }>()
display: flex; align-items: center; justify-content: center; padding: 1rem; z-index: 50;
}
.modal { width: 100%; max-width: 480px; max-height: 90vh; overflow: auto; }
+.modal--wide { max-width: 680px; }
.modal__head {
display: flex; align-items: center; justify-content: space-between;
padding: 1.1rem 1.4rem; border-bottom: 1px solid var(--line);
diff --git a/frontend/src/views/OrdersView.vue b/frontend/src/views/OrdersView.vue
index 5020344..15be7ea 100644
--- a/frontend/src/views/OrdersView.vue
+++ b/frontend/src/views/OrdersView.vue
@@ -4,6 +4,10 @@ import client from '@/api/client'
import { list } from '@/api/resources'
import { useAuthStore } from '@/stores/auth'
import Modal from '@/components/Modal.vue'
+import * as pdfjsLib from 'pdfjs-dist'
+import pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
+
+pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl
interface OrderSummary {
id: string; number: string; status: string
@@ -50,6 +54,9 @@ const form = ref<{ note: string; items: { product: string; employee: string; qua
const detail = ref(null)
const busy = ref(false)
+// Vorschau der gerenderten Karte (Mitarbeiter × Produkt) vor dem Bestellen
+const preview = ref<{ employee: string; product: string; loading: boolean; pages: string[] } | null>(null)
+
async function load() {
loading.value = true
const { data } = await client.get<{ member: OrderSummary[] }>('/orders')
@@ -116,6 +123,40 @@ async function openPdf(item: OrderItem) {
window.open(URL.createObjectURL(res.data as Blob), '_blank')
}
+// Rendert die druckfertige Karte (Vorder-/Rückseite) als Bild für die Vorschau.
+async function renderPreview(employeeId: string, productId: string, employeeName: string, productName: string) {
+ preview.value = { employee: employeeName, product: productName, loading: true, pages: [] }
+ try {
+ const res = await client.get(`/employees/${employeeId}/card.pdf`, { responseType: 'arraybuffer', params: { product: productId } })
+ const doc = await pdfjsLib.getDocument({ data: new Uint8Array(res.data as ArrayBuffer) }).promise
+ const pages: string[] = []
+ for (let p = 1; p <= doc.numPages; p++) {
+ const page = await doc.getPage(p)
+ const base = page.getViewport({ scale: 1 })
+ const vp = page.getViewport({ scale: 300 / base.width })
+ const canvas = document.createElement('canvas')
+ canvas.width = vp.width
+ canvas.height = vp.height
+ await page.render({ canvas, canvasContext: canvas.getContext('2d')!, viewport: vp }).promise
+ pages.push(canvas.toDataURL('image/png'))
+ }
+ if (preview.value) { preview.value.pages = pages; preview.value.loading = false }
+ } catch {
+ if (preview.value) preview.value.loading = false
+ }
+}
+
+function previewRow(row: { product: string; employee: string }) {
+ if (!row.product || !row.employee) return
+ const prod = products.value.find((p) => p['@id'] === row.product)
+ const emp = employees.value.find((e) => e['@id'] === row.employee)
+ renderPreview(idOf(row.employee), idOf(row.product), emp ? `${emp.firstName} ${emp.lastName}` : '', prod?.name ?? '')
+}
+
+function previewDetailItem(it: OrderItem) {
+ renderPreview(it.employee.id, it.product.id, it.employee.name, it.product.name)
+}
+
function fmtDate(s: string) { return new Date(s).toLocaleDateString('de-DE') }
onMounted(async () => { await load(); if (canOrder.value) await loadRefs() })
@@ -157,10 +198,10 @@ onMounted(async () => { await load(); if (canOrder.value) await loadRefs() })
-
+
@@ -244,13 +303,22 @@ onMounted(async () => { await load(); if (canOrder.value) await loadRefs() })
.s-cancel { background: #f3f4f6; color: #6b7280; }
.items { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
-.items-head, .item-row { display: grid; grid-template-columns: 1fr 1fr 62px 28px; gap: .35rem; align-items: center; padding: .5rem .6rem; }
+.items-head, .item-row { display: grid; grid-template-columns: 1fr 1fr 64px 32px 32px; gap: .4rem; align-items: center; padding: .5rem .6rem; }
.items-head { font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); background: #fafafa; }
.item-row { border-top: 1px solid #f4f4f4; }
.items-head > *, .item-row > * { min-width: 0; }
.item-row :deep(.input) { padding-left: .5rem; padding-right: .3rem; }
.item-row .qty { text-align: right; }
-.item-row .del { padding: 0; }
+.item-row .eye, .item-row .del { padding: 0; }
+.item-row .del { color: var(--danger); }
+
+.prev-sub { margin: -.4rem 0 1rem; }
+.prev-loading { padding: 2rem; text-align: center; color: var(--muted); }
+.prev-pages { display: flex; flex-wrap: wrap; gap: 1.2rem; justify-content: center; }
+.prev-pages figure { margin: 0; text-align: center; }
+.prev-pages img { width: 300px; max-width: 100%; border: 1px solid var(--line); border-radius: 6px; box-shadow: var(--shadow-sm); background: #fff; }
+.prev-pages figcaption { font-size: .78rem; margin-top: .4rem; }
+.prev-note { font-size: .8rem; margin: 1.1rem 0 0; text-align: center; }
.item-row .del { color: var(--danger); }
.addrow { margin: .6rem 0 1rem; }
.field { margin-top: .4rem; }