diff --git a/frontend/src/views/CardEditorView.vue b/frontend/src/views/CardEditorView.vue index 04ac6ec..9211275 100644 --- a/frontend/src/views/CardEditorView.vue +++ b/frontend/src/views/CardEditorView.vue @@ -8,6 +8,7 @@ interface Template { id: string | null; isDefault: boolean; name: string widthMm: number; heightMm: number; bleedMm: number; safeMm: number front: El[]; back: El[] + hasBackground?: boolean; fonts?: string[] } interface Company { '@id': string; id: string; name: string; slug: string; brandingConfig: Record | unknown[] } interface Employee { id: string; firstName: string; lastName: string; position: string | null; email: string | null; phone: string | null; mobile: string | null; company: string; department: string | null } @@ -168,6 +169,38 @@ async function save() { saved.value = true } finally { saving.value = false } } +// --- Druck-Assets: Hintergrund-PDF & Schriften --- +const uploading = ref(false) +function cidPath() { return selectedCompany.value?.id } + +async function uploadBackground(e: Event) { + const file = (e.target as HTMLInputElement).files?.[0] + if (!file || !tpl.value) return + uploading.value = true + try { + const fd = new FormData(); fd.append('file', file) + await client.post(`/companies/${cidPath()}/card-template/background`, fd) + tpl.value.hasBackground = true + } catch { alert('Upload fehlgeschlagen (nur PDF).') } finally { uploading.value = false; (e.target as HTMLInputElement).value = '' } +} +async function removeBackground() { + if (!tpl.value || !confirm('Hintergrund-PDF entfernen?')) return + await client.delete(`/companies/${cidPath()}/card-template/background`) + tpl.value.hasBackground = false +} +async function uploadFont(e: Event) { + const file = (e.target as HTMLInputElement).files?.[0] + if (!file || !tpl.value) return + const family = (prompt('Name der Schriftart (z. B. „Brand")', file.name.replace(/\.(ttf|otf)$/i, '')) || '').trim() + if (!family) { (e.target as HTMLInputElement).value = ''; return } + uploading.value = true + try { + const fd = new FormData(); fd.append('file', file); fd.append('family', family) + await client.post(`/companies/${cidPath()}/card-template/font`, fd) + tpl.value.fonts = [...(tpl.value.fonts ?? []).filter((f) => f !== family), family] + } catch { alert('Upload fehlgeschlagen (nur TTF/OTF).') } finally { uploading.value = false; (e.target as HTMLInputElement).value = '' } +} + async function previewPdf() { if (!sample.value) { alert('Kein Mitarbeiter in dieser Firma für die Vorschau.'); return } const res = await client.get(`/employees/${sample.value.id}/card.pdf`, { responseType: 'blob' }) @@ -210,11 +243,30 @@ onMounted(load) +
+ + + + + {{ f }} +
+
+ +
Hintergrund-PDF aktiv
sichtbar in der PDF-Vorschau
@@ -273,6 +325,15 @@ onMounted(load)
+
+ + +
@@ -308,6 +369,12 @@ onMounted(load) .tab.active { background: var(--dark); color: #fff; border-color: var(--dark); } .adds { display: flex; gap: .35rem; flex-wrap: wrap; } +.assets { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: 1rem; padding: .7rem .9rem; background: #fff; border: 1px solid var(--line); border-radius: var(--radius-sm); } +.assets .btn.disabled { opacity: .6; pointer-events: none; } +.assets .sep { width: 1px; height: 20px; background: var(--line); margin: 0 .3rem; } +.chip { background: var(--psc-orange-soft); color: var(--psc-orange-dark); padding: .15rem .6rem; border-radius: 999px; font-size: .78rem; font-weight: 600; } +.bg-note { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; color: #b3b3b3; font-size: 12px; background: repeating-linear-gradient(45deg, #fafafa, #fafafa 8px, #f2f2f2 8px, #f2f2f2 16px); pointer-events: none; } +.bg-note small { font-size: 10px; } .stage { background: #ececec; border-radius: var(--radius); padding: 1.5rem; display: flex; flex-direction: column; align-items: center; overflow-x: auto; } .bleed { position: relative; background: #fbe9da; box-shadow: var(--shadow-sm); } .trim { position: absolute; background: #fff; overflow: hidden; }