From 61a39682de84f4ec5bb00ff7db453e9783fabcc9 Mon Sep 17 00:00:00 2001 From: Fidelis Huber Date: Fri, 11 Sep 2026 08:45:31 +0200 Subject: [PATCH] =?UTF-8?q?Logo=20auf=20hellem=20Hintergrund=20+=20Export?= =?UTF-8?q?=20als=20echtes=20VenDoc-Aufma=C3=9Fblatt=20(xlsm)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Login-Logo war im hellen Modus weiß-auf-weiß unsichtbar (Karte wechselt mit dem Theme, Logo war hart auf die dunkle Variante codiert) - zeigt jetzt je nach Theme die passende Logo-Tinte, gleiches Problem beim "Willkommen"-Titel behoben. PDF-Export verlor den schwarzen Banner beim Drucken, da Browser Hintergrundfarben standardmäßig nicht drucken - jetzt über print-color-adjust erzwungen. Export ist jetzt eine echte .xlsm-Datei statt CSV, mit Spaltenlayout 1:1 aus einem echten VenDoc-Aufmaßblatt-Export des Nutzers übernommen (Blatt "Aufmaß", Position/Aufmaß/EH/Positionstext in den exakt gleichen Spalten). Co-Authored-By: Claude Sonnet 5 --- client/package.json | 3 +- client/src/pages/Login.tsx | 3 +- client/src/pages/ProjektDetail.tsx | 59 ++++++++++++++++-------------- client/src/styles.css | 16 ++++++++ 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/client/package.json b/client/package.json index 5223455..4a8dc41 100644 --- a/client/package.json +++ b/client/package.json @@ -13,7 +13,8 @@ "@azure/msal-react": "^2.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.26.2" + "react-router-dom": "^6.26.2", + "xlsx": "^0.18.5" }, "devDependencies": { "@types/react": "^18.3.9", diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx index caee85a..d9ed920 100644 --- a/client/src/pages/Login.tsx +++ b/client/src/pages/Login.tsx @@ -37,7 +37,8 @@ export default function Login({ onLocalLogin }: { onLocalLogin: (name: string) = return (
- BergVOLT Elektrotechnik + BergVOLT Elektrotechnik + BergVOLT Elektrotechnik
Materialschein
Willkommen
Melde dich mit deinem BergVOLT
Office 365 Konto an.
diff --git a/client/src/pages/ProjektDetail.tsx b/client/src/pages/ProjektDetail.tsx index fff26b9..83257c9 100644 --- a/client/src/pages/ProjektDetail.tsx +++ b/client/src/pages/ProjektDetail.tsx @@ -1,5 +1,6 @@ import { useEffect, useMemo, useState } from 'react'; import { Link, useNavigate, useParams } from 'react-router-dom'; +import * as XLSX from 'xlsx'; import { api } from '../lib/api'; import type { MaterialItem, Projekt } from '../lib/types'; import { kabelInfoForBez } from '../data/kabelData'; @@ -217,37 +218,39 @@ export default function ProjektDetail() { setRetourBusy(false); } - // ── CSV-Export (für Import in VenDoc als Auftragspositionen) ── - // Semikolon-getrennt, Komma als Dezimaltrenner, UTF-8 mit BOM – Standardkonvention - // für deutschsprachiges Excel/ERP. Spaltennamen (Position/EH/Positionstext/Aufmaß) - // richten sich nach VenDocs eigenem Aufmaßblatt-Export (Auftrag A00079/2026), das - // keine eigene Artikelnummer-Spalte führt – die Art.-Nr. wird daher wie dort in den - // Positionstext eingebettet. Ob das exakte Spaltenformat vom Universalimport 1:1 - // übernommen wird, ist ungeprüft – ggf. anhand des ersten Testimports nachschärfen. + // ── XLSM-Export (für Import in VenDoc als Auftragspositionen) ── + // Spaltenlayout und -namen sind 1:1 aus einem echten VenDoc-Aufmaßblatt-Export des + // Nutzers (Auftrag A00079/2026, Blatt "Aufmaß") übernommen: Position in Spalte A, + // Aufmaß (Menge) in H, EH (Einheit) in I, Positionstext in K. VenDoc führt dort keine + // eigene Artikelnummer-Spalte – die Art.-Nr. wird daher wie im Original in den + // Positionstext eingebettet. Oid/PosTyp/Lohn/Material bleiben leer, da das echte + // VenDoc-Aufmaßblatt bestehende Positionen per interner Oid referenziert, die wir + // nicht kennen/vergeben können. const EH_MAP: Record = { Stk: 'ST', m: 'M' }; // aus VenDoc-Beispiel bestätigt - function csvEscape(v: string): string { - if (/[;"\n\r]/.test(v)) return '"' + v.replace(/"/g, '""') + '"'; - return v; - } - function exportCSV() { + function exportXlsm() { if (!projekt) return; - const header = ['Position', 'EH', 'Positionstext', 'Aufmaß']; - const rows = matList.map((r, i) => [ - String(i + 1), - EH_MAP[r.einheit] ?? r.einheit, - r.artNr ? `${r.bezeichnung} (${r.artNr})` : r.bezeichnung, - fmt1(r.menge).replace('.', ',') - ]); - const csv = [header, ...rows].map(row => row.map(csvEscape).join(';')).join('\r\n'); + const aoa: (string | number)[][] = [ + ['Projekt:'], + ['Betreff', '', '', '', '', titel || ''], + ['Auftrag', '', '', '', '', auftragsNr || ''], + [], + ['Position ', 'PosTyp', 'Dummy2', 'Dummy3', 'Oid', 'Ausschr.', 'Zugebu.', 'Aufmaß', 'EH', '', 'Positionstext'] + ]; + matList.forEach((r, i) => { + const row: (string | number)[] = []; + row[0] = i + 1; + row[7] = num1(r.menge); + row[8] = EH_MAP[r.einheit] ?? r.einheit; + row[10] = r.artNr ? `${r.bezeichnung} (${r.artNr})` : r.bezeichnung; + aoa.push(row); + }); + const ws = XLSX.utils.aoa_to_sheet(aoa); + ws['!cols'] = [{ wch: 10 }, { wch: 8 }, { wch: 8 }, { wch: 8 }, { wch: 8 }, { wch: 9 }, { wch: 9 }, { wch: 9 }, { wch: 7 }, { wch: 2 }, { wch: 55 }]; + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, 'Aufmaß'); const safeName = (titel || 'Materialschein').replace(/[^\p{L}\p{N}_-]+/gu, '_'); const safeAuftrag = auftragsNr ? auftragsNr.replace(/[^\p{L}\p{N}_-]+/gu, '_') + '_' : ''; - const filename = `Materialschein_${safeAuftrag}${safeName}.csv`; - const blob = new Blob(['' + csv], { type: 'text/csv;charset=utf-8;' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; a.download = filename; - document.body.appendChild(a); a.click(); a.remove(); - URL.revokeObjectURL(url); + XLSX.writeFile(wb, `Materialschein_${safeAuftrag}${safeName}.xlsm`, { bookType: 'xlsm' }); } if (loading) return
; @@ -477,7 +480,7 @@ export default function ProjektDetail() {
- +
); diff --git a/client/src/styles.css b/client/src/styles.css index e0757fa..ccc88fa 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -57,8 +57,20 @@ button,input,select,textarea{font-family:inherit} .login-screen::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 70% 60% at 30% 30%,rgba(0,200,216,.08) 0%,transparent 70%)} .lcard{background:var(--card);border:1px solid var(--bor);border-radius:var(--r);box-shadow:var(--sh);padding:44px 36px;width:380px;max-width:100%;text-align:center;position:relative;z-index:1} .llogo-img{height:46px;margin:0 auto;display:block} +/* .lcard wechselt mit dem Theme zwischen dunkel und hell (siehe --card oben) – daher + je nach Kartenhintergrund die passende Logovariante zeigen (helle Tinte auf dunkel, + dunkle Tinte auf hell), sonst verschwindet das Logo im hellen Modus auf weiß. */ +.llogo-onlight{display:none} +:root[data-theme="light"] .llogo-ondark{display:none} +:root[data-theme="light"] .llogo-onlight{display:block} +@media(prefers-color-scheme:light){ + :root:not([data-theme="dark"]) .llogo-ondark{display:none} + :root:not([data-theme="dark"]) .llogo-onlight{display:block} +} .lsub{font-size:10px;letter-spacing:5px;text-transform:uppercase;color:var(--cyan);opacity:.8;margin-top:8px;margin-bottom:26px} .ltitle{font-size:16px;font-weight:700;color:#fff;margin-bottom:8px} +:root[data-theme="light"] .ltitle{color:var(--text)} +@media(prefers-color-scheme:light){:root:not([data-theme="dark"]) .ltitle{color:var(--text)}} .ldesc{color:var(--muted);font-size:13px;margin-bottom:26px;line-height:1.6} .btn-ms{display:flex;align-items:center;justify-content:center;gap:10px;background:#0078d4;color:#fff;border:none;border-radius:9px;padding:14px;width:100%;font-size:14px;font-weight:700;cursor:pointer} .btn-ms:hover{background:#1084d8} @@ -224,6 +236,10 @@ table.pinfo td:last-child{text-align:right;font-weight:600} @media print{ header,.tabs,.print-bar,.detail-back,.kab-card,.manual-card,.baustelle-card,.card-hint,.del-btn,.qty-input,.notiz-input, .chk-col,.retour-panel,.btn-retour-toggle{display:none!important} + /* Ohne dies lassen viele Browser Hintergrundfarben beim Drucken/PDF-Export weg + (Häkchen "Hintergrundgrafiken" aus) – dann verschwindet der schwarze Banner + und das helle Logo wirkt "weiß"/unsichtbar auf der weißen Seite. */ + .print-doc,.print-doc *{-webkit-print-color-adjust:exact;print-color-adjust:exact;color-adjust:exact} body{background:#fff;color:#000} main{max-width:none;padding:0;margin:0} .card{box-shadow:none;border:none;background:#fff;padding:0 4px;page-break-inside:avoid}