OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

voucher-label.js

13 KB
/*
 * Voucher-Etikettendruck (UniFi) – gemeinsame Client-Logik für Voucher-Seite,
 * Dashboard-Widget und Ticket-Schnellaktion.
 *
 * Konfiguration pro Seite über window.VoucherLabelConfig:
 *   {
 *     printMethod: 'both' | 'dymo_connect' | 'windows_print',
 *     logoUrl:     '/uploads/voucher_label_logo/...'  (oder ''),
 *     geo:         { w,h,pad,gapInner,logoH,logoW,font, ... }  (DymoLabel::windowsGeo),
 *     apiBase:     '/unifi/vouchers/dymo-label'
 *   }
 *
 * Öffentliche API:  window.VoucherLabel
 *   .windows(code)      – Windows-Druckdialog
 *   .dymo(code)         – DYMO-Connect-Modal (Drucker wählen + drucken)
 *   .buttons(code,opts) – HTML-String mit Druck-Buttons je nach printMethod
 */
(function () {
    'use strict';

    function cfg() { return window.VoucherLabelConfig || {}; }

    function esc(s) {
        return String(s == null ? '' : s)
            .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
    }

    function fmtCode(code) {
        var c = String(code).replace(/[-\s]/g, '');
        return c.length >= 10 ? c.slice(0, 5) + '-' + c.slice(5) : String(code);
    }

    // -----------------------------------------------------------------
    // Windows-Druckdialog: Logo oben, Code groß zentriert darunter
    // -----------------------------------------------------------------
    function printWindows(code) {
        var c = cfg();
        var g = c.geo || { w: 54, h: 25, pad: 1.5, gapInner: 1, logoH: 9, logoW: 30, font: 8 };
        var logo = c.logoUrl || '';
        var disp = fmtCode(code);

        var css = [
            '@page { size: ' + g.w + 'mm ' + g.h + 'mm; margin: 0; }',
            'body { margin:0; padding:0; background:#fff; }',
            '.lbl { width:' + g.w + 'mm; height:' + g.h + 'mm; display:flex; flex-direction:column;',
            '  align-items:center; justify-content:center; gap:' + g.gapInner + 'mm;',
            '  padding:' + g.pad + 'mm; box-sizing:border-box; overflow:hidden; }',
            '.lbl img { max-height:' + g.logoH + 'mm; max-width:' + g.logoW + 'mm; object-fit:contain; }',
            '.lbl span { font-family:"Courier New",monospace; font-size:' + (g.font * 1.4) + 'pt; font-weight:bold;',
            '  letter-spacing:.04em; text-align:center; word-break:break-all; color:#000; }'
        ].join('\n');

        var body = '<div class="lbl">'
            + (logo ? '<img src="' + esc(logo) + '" alt="">' : '')
            + '<span>' + esc(disp) + '</span></div>';

        var win = window.open('', '_blank', 'width=320,height=220');
        if (!win) { alert(window.onitText('Bitte Popup-Fenster für diese Seite erlauben.')); return; }
        win.document.write('<!DOCTYPE html><html><head><meta charset="utf-8"><style>' + css + '</style></head><body>' + body + '</body></html>');
        win.document.close();
        win.focus();
        setTimeout(function () {
            win.print();
            win.onafterprint = function () { win.close(); };
        }, 200);
    }

    // -----------------------------------------------------------------
    // DYMO Connect: Label-XML vom Server holen, Drucker wählen, drucken
    // -----------------------------------------------------------------
    var DYMO_BASES = ['https://localhost:41952', 'http://localhost:41951'];
    var modalEl = null;
    var state = { base: null, xml: null, code: null };

    function ensureModal() {
        if (modalEl) return modalEl;
        modalEl = document.createElement('div');
        modalEl.id = 'vlbl-modal';
        modalEl.style.cssText = 'position:fixed;inset:0;background:rgba(15,23,42,.5);z-index:100020;display:none;align-items:center;justify-content:center;';
        modalEl.innerHTML =
            '<div style="background:#fff;border-radius:.6rem;box-shadow:0 20px 60px rgba(0,0,0,.3);width:min(420px,calc(100vw - 2rem));overflow:hidden;">'
            + '<div style="display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid #e5e7eb;font-weight:600;">'
            + '<span>🏷️ ' + window.onitText('Voucher-Etikett drucken') + '</span>'
            + '<button type="button" id="vlbl-close" style="background:none;border:none;font-size:1.3rem;line-height:1;cursor:pointer;color:#6b7280;">&times;</button>'
            + '</div>'
            + '<div id="vlbl-body" style="padding:1.1rem 1.25rem;font-size:.88rem;color:#374151;"></div>'
            + '</div>';
        document.body.appendChild(modalEl);
        modalEl.addEventListener('click', function (e) { if (e.target === modalEl) closeModal(); });
        modalEl.querySelector('#vlbl-close').addEventListener('click', closeModal);
        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape' && modalEl.style.display === 'flex') closeModal();
        });
        return modalEl;
    }

    function setBody(html) {
        ensureModal().querySelector('#vlbl-body').innerHTML = html;
    }

    function openModal() { ensureModal().style.display = 'flex'; }
    function closeModal() { if (modalEl) modalEl.style.display = 'none'; }

    function openDymo(code) {
        state = { base: null, xml: null, code: code };
        ensureModal();
        openModal();
        initDymo();
    }

    async function initDymo() {
        setBody('<div style="text-align:center;padding:1.5rem;color:#6b7280;">⏳ Verbinde mit DYMO Connect…</div>');

        // 1) Label-XML vom Server laden
        try {
            var apiBase = cfg().apiBase || '/unifi/vouchers/dymo-label';
            var r = await fetch(apiBase + '?code=' + encodeURIComponent(state.code), { credentials: 'same-origin' });
            if (r.ok) {
                var data = await r.json();
                if (data && data.labelXml) state.xml = data.labelXml;
            }
        } catch (e) { /* ignore */ }

        if (!state.xml) {
            setBody('<div style="color:#dc2626;">' + window.onitText('Etikett konnte nicht erzeugt werden. Bitte später erneut versuchen.') + '</div>'
                + footerBtns());
            bindFooter();
            return;
        }

        // 2) DYMO Web Service suchen
        for (var i = 0; i < DYMO_BASES.length; i++) {
            try {
                var pr = await fetch(DYMO_BASES[i] + '/DYMO/DLS/Printing/GetPrinters', { signal: AbortSignal.timeout(3000) });
                if (pr.ok) { state.base = DYMO_BASES[i]; break; }
            } catch (e) { /* try next */ }
        }

        if (!state.base) {
            setBody(
                '<div style="color:#dc2626;font-weight:700;margin-bottom:.5rem;">⚠ DYMO Connect nicht gefunden</div>'
                + '<p style="font-size:.83rem;margin:0 0 .4rem;">Bitte sicherstellen, dass <strong>DYMO Connect</strong> installiert und gestartet ist.</p>'
                + '<p style="font-size:.8rem;color:#6b7280;margin:0 0 .75rem;">Bei DYMO Connect zuerst <a href="https://localhost:41952" target="_blank" style="color:#3c8dbc;">https://localhost:41952</a> aufrufen und das Zertifikat akzeptieren, dann erneut versuchen.</p>'
                + '<div style="display:flex;gap:.5rem;"><button type="button" id="vlbl-retry" class="btn btn-primary" style="font-size:.82rem;">Erneut versuchen</button>'
                + '<button type="button" id="vlbl-cancel" class="btn btn-default" style="font-size:.82rem;">' + window.onitText('Schließen') + '</button></div>'
            );
            bindFooter();
            return;
        }

        // 3) Drucker auflisten
        var printers = [];
        try {
            var pr2 = await fetch(state.base + '/DYMO/DLS/Printing/GetPrinters');
            var xml = await pr2.text();
            var doc = (new DOMParser()).parseFromString(xml, 'text/xml');
            printers = Array.from(doc.querySelectorAll('LabelWriterPrinter Name'))
                .map(function (n) { return n.textContent.trim(); })
                .filter(Boolean);
        } catch (e) { /* ignore */ }

        if (printers.length === 0) {
            setBody('<div style="color:#d97706;">' + window.onitText('Kein DYMO LabelWriter-Drucker gefunden. Bitte Drucker anschließen und DYMO Connect neu starten.') + '</div>'
                + footerBtns());
            bindFooter();
            return;
        }

        var options = printers.map(function (p) {
            return '<option value="' + esc(p) + '">' + esc(p) + '</option>';
        }).join('');

        var preview = (cfg().logoUrl ? '<img src="' + esc(cfg().logoUrl) + '" style="max-height:34px;max-width:150px;object-fit:contain;display:block;margin:0 auto .35rem;">' : '')
            + '<div style="font-family:\'Courier New\',monospace;font-size:1rem;font-weight:bold;text-align:center;letter-spacing:.04em;color:#111;">' + esc(fmtCode(state.code)) + '</div>';

        setBody(
            '<div style="margin-bottom:.85rem;">'
            + '<label style="font-size:.8rem;font-weight:700;color:#374151;display:block;margin-bottom:.3rem;">Drucker</label>'
            + '<select id="vlbl-printer" class="form-control" style="width:100%;">' + options + '</select>'
            + '</div>'
            + '<div style="margin-bottom:.85rem;">'
            + '<div style="font-size:.8rem;font-weight:700;color:#374151;margin-bottom:.35rem;">Vorschau</div>'
            + '<div style="border:1px dashed #cbd5e1;border-radius:.4rem;padding:.6rem;background:#f8fafc;">' + preview + '</div>'
            + '</div>'
            + '<div style="display:flex;gap:.5rem;align-items:center;">'
            + '<button type="button" id="vlbl-print" class="btn btn-primary">🖨️ Drucken</button>'
            + '<button type="button" id="vlbl-cancel" class="btn btn-default">' + window.onitText('Schließen') + '</button>'
            + '</div>'
            + '<div id="vlbl-status" style="display:none;margin-top:.6rem;font-size:.82rem;"></div>'
        );
        bindFooter();
        var printBtn = modalEl.querySelector('#vlbl-print');
        if (printBtn) printBtn.addEventListener('click', doPrint);
    }

    function footerBtns() {
        return '<div style="display:flex;gap:.5rem;margin-top:.9rem;">'
            + '<button type="button" id="vlbl-retry" class="btn btn-primary" style="font-size:.82rem;">Erneut versuchen</button>'
            + '<button type="button" id="vlbl-cancel" class="btn btn-default" style="font-size:.82rem;">' + window.onitText('Schließen') + '</button></div>';
    }

    function bindFooter() {
        var retry = modalEl.querySelector('#vlbl-retry');
        var cancel = modalEl.querySelector('#vlbl-cancel');
        if (retry) retry.addEventListener('click', initDymo);
        if (cancel) cancel.addEventListener('click', closeModal);
    }

    async function doPrint() {
        var sel = modalEl.querySelector('#vlbl-printer');
        var btn = modalEl.querySelector('#vlbl-print');
        var status = modalEl.querySelector('#vlbl-status');
        if (!sel || !state.xml || !state.base) return;

        btn.disabled = true;
        if (status) { status.style.display = 'block'; status.style.color = '#6b7280'; status.textContent = '⧖ Wird gedruckt…'; }

        try {
            var body = new URLSearchParams({ printerName: sel.value, labelXml: state.xml });
            var r = await fetch(state.base + '/DYMO/DLS/Printing/PrintLabel', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: body.toString()
            });
            if (r.ok) {
                if (status) { status.style.color = '#059669'; status.textContent = '✓ Etikett wurde gedruckt!'; }
            } else {
                var err = await r.text().catch(function () { return r.statusText; });
                if (status) { status.style.color = '#dc2626'; status.textContent = 'Fehler: ' + err; }
            }
        } catch (e) {
            if (status) { status.style.color = '#dc2626'; status.textContent = 'Druckfehler: ' + e.message; }
        } finally {
            btn.disabled = false;
        }
    }

    // -----------------------------------------------------------------
    // Druck-Buttons (HTML) je nach konfigurierter Methode
    // -----------------------------------------------------------------
    function buttonsHtml(code, opts) {
        opts = opts || {};
        var method = cfg().printMethod || 'both';
        var cls = opts.btnClass || 'btn btn-xs btn-default';
        var ec = esc(String(code).replace(/[^A-Za-z0-9\- ]/g, ''));
        var html = '';
        if (method === 'dymo_connect' || method === 'both') {
            html += '<button type="button" class="' + cls + '" onclick="VoucherLabel.dymo(\'' + ec + '\')" title="' + window.onitText('Etikett über DYMO Connect drucken') + '">🏷️ DYMO</button> ';
        }
        if (method === 'windows_print' || method === 'both') {
            html += '<button type="button" class="' + cls + '" onclick="VoucherLabel.windows(\'' + ec + '\')" title="' + window.onitText('Etikett über Windows-Druckdialog drucken') + '">🏷️ Etikett</button>';
        }
        return html;
    }

    window.VoucherLabel = {
        windows: printWindows,
        dymo: openDymo,
        buttons: buttonsHtml,
        configure: function (c) { window.VoucherLabelConfig = c; }
    };
})();