- Menü aus Header entfernt, neuer Eintrag unter Produkte > Scanner - Barcode-Erkennung: patchSize medium, grösserer Scan-Bereich, höhere Frequenz - Timeout-Hinweis nach 8s wenn kein Barcode erkannt wird - Tab-Wechsel (Order/Shop/Inventur) ohne Seitenreload, Kamera bleibt aktiv - PWA: gleiche Tab-Logik, Buttons statt Links - Changelog und README aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
829 lines
22 KiB
PHP
Executable file
829 lines
22 KiB
PHP
Executable file
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
*
|
|
* PWA Standalone Scanner - Mit eigenem Login (15 Tage gespeichert)
|
|
*/
|
|
|
|
// Kein Login erforderlich - wird via JavaScript geprueft
|
|
if (!defined('NOLOGIN')) {
|
|
define('NOLOGIN', '1');
|
|
}
|
|
if (!defined('NOREQUIREMENU')) {
|
|
define('NOREQUIREMENU', '1');
|
|
}
|
|
|
|
// Load Dolibarr environment
|
|
$res = 0;
|
|
if (!$res && file_exists("../main.inc.php")) {
|
|
$res = @include "../main.inc.php";
|
|
}
|
|
if (!$res && file_exists("../../main.inc.php")) {
|
|
$res = @include "../../main.inc.php";
|
|
}
|
|
if (!$res && file_exists("../../../main.inc.php")) {
|
|
$res = @include "../../../main.inc.php";
|
|
}
|
|
if (!$res) {
|
|
die("Dolibarr konnte nicht geladen werden");
|
|
}
|
|
|
|
// Load translation files
|
|
$langs->loadLangs(array("handybarcodescanner@handybarcodescanner", "products", "orders", "stocks"));
|
|
|
|
// Get parameters
|
|
$mode = GETPOST('mode', 'alpha') ?: 'order';
|
|
|
|
// Check mode-specific permissions
|
|
$enableOrder = getDolGlobalInt('HANDYBARCODESCANNER_ENABLE_ORDER', 1);
|
|
$enableShop = getDolGlobalInt('HANDYBARCODESCANNER_ENABLE_SHOP', 1);
|
|
$enableInventory = getDolGlobalInt('HANDYBARCODESCANNER_ENABLE_INVENTORY', 1);
|
|
|
|
// Get Dolibarr theme colors
|
|
$colormain = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#0077b3');
|
|
|
|
?><!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<meta name="theme-color" content="<?php echo $colormain; ?>">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="Scanner">
|
|
<title>Barcode Scanner</title>
|
|
<link rel="manifest" href="manifest.json">
|
|
<link rel="apple-touch-icon" href="img/icon-192.png">
|
|
<link rel="stylesheet" href="css/scanner.css">
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
:root {
|
|
--primary: <?php echo $colormain; ?>;
|
|
--colorbackbody: #1d1e20;
|
|
--colorbackcard: #1d1e20;
|
|
--colorbacktitle: #3b3c3e;
|
|
--colorbackline: #38393d;
|
|
--colorbackinput: rgb(70, 70, 70);
|
|
--colortext: rgb(220,220,220);
|
|
--colortextmuted: rgb(180,180,180);
|
|
--colortextlink: #4390dc;
|
|
--colorborder: #2b2c2e;
|
|
--butactionbg: rgb(173,140,79);
|
|
--textbutaction: rgb(255,255,255);
|
|
--success: #25a580;
|
|
--danger: #993013;
|
|
--warning: #bc9526;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
background: var(--colorbackbody);
|
|
color: var(--colortext);
|
|
min-height: 100vh;
|
|
min-height: 100dvh;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.app {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
min-height: 100dvh;
|
|
}
|
|
|
|
/* Login Screen */
|
|
.login-screen {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
.login-box {
|
|
width: 100%;
|
|
max-width: 340px;
|
|
background: var(--colorbackline);
|
|
border-radius: 12px;
|
|
padding: 30px 24px;
|
|
border: 1px solid var(--colorborder);
|
|
}
|
|
|
|
.login-logo {
|
|
text-align: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.login-logo svg {
|
|
width: 64px;
|
|
height: 64px;
|
|
fill: var(--primary);
|
|
}
|
|
|
|
.login-title {
|
|
text-align: center;
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.login-subtitle {
|
|
text-align: center;
|
|
font-size: 14px;
|
|
color: var(--colortextmuted);
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.login-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.form-label {
|
|
font-size: 14px;
|
|
color: var(--colortextmuted);
|
|
}
|
|
|
|
.form-input {
|
|
padding: 14px 16px;
|
|
font-size: 16px;
|
|
background: var(--colorbackinput);
|
|
border: 1px solid var(--colorborder);
|
|
border-radius: 8px;
|
|
color: var(--colortext);
|
|
outline: none;
|
|
}
|
|
|
|
.form-input:focus {
|
|
border-color: var(--primary);
|
|
}
|
|
|
|
.remember-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.remember-row input[type="checkbox"] {
|
|
width: 20px;
|
|
height: 20px;
|
|
accent-color: var(--primary);
|
|
}
|
|
|
|
.remember-row label {
|
|
font-size: 14px;
|
|
color: var(--colortextmuted);
|
|
}
|
|
|
|
.login-btn {
|
|
padding: 16px;
|
|
font-size: 17px;
|
|
font-weight: 600;
|
|
background: var(--butactionbg);
|
|
color: var(--textbutaction);
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.login-btn:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.login-error {
|
|
background: var(--danger);
|
|
color: white;
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
text-align: center;
|
|
display: none;
|
|
}
|
|
|
|
.login-info {
|
|
background: var(--colorbacktitle);
|
|
padding: 12px;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
color: var(--colortextmuted);
|
|
text-align: center;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
/* Header */
|
|
.header {
|
|
background: var(--primary);
|
|
color: #fff;
|
|
padding: 14px 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
}
|
|
|
|
.header-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.header-user {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.header-username {
|
|
font-size: 13px;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.header-logout {
|
|
background: rgba(255,255,255,0.2);
|
|
border: none;
|
|
color: white;
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Mode Tabs */
|
|
.mode-tabs {
|
|
display: flex;
|
|
background: var(--colorbacktitle);
|
|
border-bottom: 1px solid var(--colorborder);
|
|
}
|
|
|
|
.mode-tab {
|
|
flex: 1;
|
|
padding: 14px 8px;
|
|
text-align: center;
|
|
color: var(--colortextmuted);
|
|
text-decoration: none;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
border-bottom: 3px solid transparent;
|
|
transition: all 0.2s;
|
|
cursor: pointer;
|
|
background: none;
|
|
border-left: none;
|
|
border-right: none;
|
|
border-top: none;
|
|
}
|
|
|
|
.mode-tab.active {
|
|
color: var(--primary);
|
|
border-bottom-color: var(--primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Scanner Area */
|
|
.scanner-area {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 12px;
|
|
}
|
|
|
|
.scanner-box {
|
|
background: #333;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.scanner-video-box {
|
|
position: relative;
|
|
width: 100%;
|
|
aspect-ratio: 4/3;
|
|
max-height: 35vh;
|
|
background: #000;
|
|
}
|
|
|
|
#scanner-video {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.scan-region-highlight {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 70%;
|
|
height: 50%;
|
|
border: 3px solid var(--primary);
|
|
border-radius: 8px;
|
|
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.scanner-video-box.scanning .scan-region-highlight {
|
|
animation: pulse 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { border-color: var(--primary); }
|
|
50% { border-color: var(--success); }
|
|
}
|
|
|
|
.scanner-controls {
|
|
padding: 10px;
|
|
background: #444;
|
|
text-align: center;
|
|
}
|
|
|
|
.scan-btn {
|
|
min-width: 180px;
|
|
padding: 14px 28px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.scan-btn.start {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
|
|
.scan-btn.stop {
|
|
background: var(--danger);
|
|
color: white;
|
|
}
|
|
|
|
/* Last Scan */
|
|
.scanner-last-scan {
|
|
background: var(--colorbackline);
|
|
padding: 12px 16px;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
border: 1px solid var(--colorborder);
|
|
}
|
|
|
|
.last-scan-label {
|
|
color: var(--colortextmuted);
|
|
font-size: 14px;
|
|
}
|
|
|
|
#last-scan-code {
|
|
font-family: monospace;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--colortextlink);
|
|
}
|
|
|
|
#result-area {
|
|
flex: 1;
|
|
}
|
|
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
/* Toast */
|
|
.scanner-toast {
|
|
position: fixed;
|
|
bottom: 80px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
padding: 14px 28px;
|
|
border-radius: 10px;
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
z-index: 10000;
|
|
}
|
|
|
|
.scanner-toast.success {
|
|
background: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
.scanner-toast.error {
|
|
background: var(--danger);
|
|
color: white;
|
|
}
|
|
|
|
/* Loading Overlay */
|
|
.loading-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: var(--colorbackbody);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 9999;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 50px;
|
|
height: 50px;
|
|
border: 4px solid var(--colorborder);
|
|
border-top-color: var(--primary);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Dolibarr Dark Theme Overrides */
|
|
.product-card { background: var(--colorbackline); border: 1px solid var(--colorborder); color: var(--colortext); }
|
|
.product-name { color: var(--colortext); }
|
|
.product-ref { color: var(--colortextmuted); }
|
|
.product-stock { background: var(--colorbacktitle); color: var(--colortext); }
|
|
.supplier-option { background: var(--colorbackline); border: 2px solid var(--colorborder); color: var(--colortext); }
|
|
.supplier-option.selected { border-color: var(--butactionbg); background: rgba(173, 140, 79, 0.15); }
|
|
.supplier-name { color: var(--colortext); }
|
|
.supplier-price { color: var(--colortextlink); }
|
|
.qty-btn { background: var(--colorbacktitle); border: 1px solid var(--colorborder); color: var(--colortext); }
|
|
.qty-input { background: var(--colorbackinput); border: 1px solid var(--colorborder); color: var(--colortext); }
|
|
.action-btn { width: 100%; padding: 16px; font-size: 17px; font-weight: 600; border: none; border-radius: 3px; cursor: pointer; margin-top: 16px; text-transform: uppercase; }
|
|
.action-btn.btn-primary, .btn-primary { background: var(--butactionbg) !important; color: var(--textbutaction) !important; }
|
|
.stock-display { background: var(--colorbackline); border: 1px solid var(--colorborder); }
|
|
.stock-value { color: var(--colortextlink); }
|
|
.shop-link-btn { background: var(--colorbacktitle); border: 1px solid var(--colorborder); color: var(--colortext); }
|
|
.confirm-dialog { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; z-index: 10000; padding: 20px; }
|
|
.confirm-content { background: var(--colorbackline); border-radius: 8px; padding: 24px; width: 100%; max-width: 320px; text-align: center; border: 1px solid var(--colorborder); }
|
|
.confirm-title { font-size: 18px; font-weight: 600; margin-bottom: 12px; color: var(--colortext); }
|
|
.confirm-message { font-size: 14px; color: var(--colortextmuted); margin-bottom: 24px; }
|
|
.confirm-buttons { display: flex; gap: 12px; }
|
|
.confirm-buttons .action-btn { flex: 1; margin-top: 0; }
|
|
.action-btn.btn-secondary, .btn-secondary { background: var(--colorbacktitle) !important; color: var(--colortext) !important; border: 1px solid var(--colorborder); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Loading Overlay -->
|
|
<div id="loading-overlay" class="loading-overlay">
|
|
<div class="loading-spinner"></div>
|
|
</div>
|
|
|
|
<!-- Login Screen -->
|
|
<div id="login-screen" class="login-screen hidden">
|
|
<div class="login-box">
|
|
<div class="login-logo">
|
|
<svg viewBox="0 0 24 24"><path d="M2 6h2v12H2V6zm3 0h1v12H5V6zm2 0h2v12H7V6zm3 0h1v12h-1V6zm2 0h3v12h-3V6zm4 0h1v12h-1V6zm2 0h1v12h-1V6zm2 0h2v12h-2V6z"/></svg>
|
|
</div>
|
|
<h1 class="login-title">Barcode Scanner</h1>
|
|
<p class="login-subtitle">Anmelden um fortzufahren</p>
|
|
|
|
<div id="login-error" class="login-error"></div>
|
|
|
|
<form id="login-form" class="login-form">
|
|
<div class="form-group">
|
|
<label class="form-label">Benutzername</label>
|
|
<input type="text" id="login-user" class="form-input" autocomplete="username" autocapitalize="none" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Passwort</label>
|
|
<input type="password" id="login-pass" class="form-input" autocomplete="current-password" required>
|
|
</div>
|
|
<div class="remember-row">
|
|
<input type="checkbox" id="login-remember" checked>
|
|
<label for="login-remember">Angemeldet bleiben (15 Tage)</label>
|
|
</div>
|
|
<button type="submit" id="login-btn" class="login-btn">Anmelden</button>
|
|
</form>
|
|
|
|
<div class="login-info">
|
|
Verwende deine Dolibarr-Zugangsdaten
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scanner App -->
|
|
<div id="scanner-app" class="app hidden">
|
|
<!-- Header -->
|
|
<header class="header">
|
|
<span class="header-title">Barcode Scanner</span>
|
|
<div class="header-user">
|
|
<span id="header-username" class="header-username"></span>
|
|
<button type="button" id="logout-btn" class="header-logout">Abmelden</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Mode Tabs (kein Seitenreload, Kamera bleibt aktiv) -->
|
|
<nav class="mode-tabs">
|
|
<?php if ($enableOrder): ?>
|
|
<button type="button" class="mode-tab scanner-tab <?php echo $mode === 'order' ? 'active' : ''; ?>" data-mode="order" onclick="scannerSwitchMode('order', this)">Bestellen</button>
|
|
<?php endif; ?>
|
|
<?php if ($enableShop): ?>
|
|
<button type="button" class="mode-tab scanner-tab <?php echo $mode === 'shop' ? 'active' : ''; ?>" data-mode="shop" onclick="scannerSwitchMode('shop', this)">Shop</button>
|
|
<?php endif; ?>
|
|
<?php if ($enableInventory): ?>
|
|
<button type="button" class="mode-tab scanner-tab <?php echo $mode === 'inventory' ? 'active' : ''; ?>" data-mode="inventory" onclick="scannerSwitchMode('inventory', this)">Inventur</button>
|
|
<?php endif; ?>
|
|
</nav>
|
|
<script>
|
|
function scannerSwitchMode(mode, btn) {
|
|
document.querySelectorAll('.scanner-tab').forEach(function(t) { t.classList.remove('active'); });
|
|
btn.classList.add('active');
|
|
if (typeof SCANNER_CONFIG !== 'undefined' && SCANNER_CONFIG) SCANNER_CONFIG.mode = mode;
|
|
if (typeof window.SCANNER_CONFIG !== 'undefined' && window.SCANNER_CONFIG) window.SCANNER_CONFIG.mode = mode;
|
|
history.replaceState(null, '', window.location.pathname + '?mode=' + mode);
|
|
if (typeof window._scannerHideResult === 'function') window._scannerHideResult();
|
|
if (window._scannerCurrentProduct && typeof window._scannerShowResult === 'function') {
|
|
window._scannerShowResult(window._scannerCurrentProduct);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<!-- Scanner Area -->
|
|
<main class="scanner-area">
|
|
<div class="scanner-box">
|
|
<div id="scanner-video-container" class="scanner-video-box">
|
|
<video id="scanner-video" playsinline></video>
|
|
<div class="scan-region-highlight"></div>
|
|
</div>
|
|
<div class="scanner-controls">
|
|
<button type="button" id="start-scan-btn" class="scan-btn start">Scannen starten</button>
|
|
<button type="button" id="stop-scan-btn" class="scan-btn stop hidden">Scannen stoppen</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="scanner-last-scan">
|
|
<span class="last-scan-label">Letzter Scan:</span>
|
|
<span id="last-scan-code">-</span>
|
|
</div>
|
|
|
|
<div id="result-area" class="scanner-result hidden"></div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
// PWA Auth Manager
|
|
const PWA_AUTH = {
|
|
STORAGE_KEY: 'hbs_pwa_auth',
|
|
ajaxUrl: '<?php echo dol_buildpath('/handybarcodescanner/ajax/', 1); ?>',
|
|
|
|
// Gespeicherte Auth-Daten laden
|
|
getStoredAuth: function() {
|
|
try {
|
|
const data = localStorage.getItem(this.STORAGE_KEY);
|
|
if (!data) return null;
|
|
const auth = JSON.parse(data);
|
|
// Ablauf pruefen
|
|
if (auth.expires && auth.expires < Date.now() / 1000) {
|
|
this.clearAuth();
|
|
return null;
|
|
}
|
|
return auth;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
// Auth-Daten speichern
|
|
saveAuth: function(data) {
|
|
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
|
|
},
|
|
|
|
// Auth-Daten loeschen
|
|
clearAuth: function() {
|
|
localStorage.removeItem(this.STORAGE_KEY);
|
|
},
|
|
|
|
// Login durchfuehren
|
|
login: function(username, password, remember) {
|
|
return fetch(this.ajaxUrl + 'pwa_login.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
body: 'login=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password)
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success && remember) {
|
|
this.saveAuth({
|
|
pwa_token: data.pwa_token,
|
|
csrf_token: data.csrf_token,
|
|
user: data.user,
|
|
expires: data.expires
|
|
});
|
|
}
|
|
return data;
|
|
});
|
|
},
|
|
|
|
// Token verifizieren und Session erneuern
|
|
verify: function() {
|
|
const auth = this.getStoredAuth();
|
|
if (!auth || !auth.pwa_token) {
|
|
return Promise.resolve({ success: false, need_login: true });
|
|
}
|
|
|
|
return fetch(this.ajaxUrl + 'pwa_verify.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-PWA-Token': auth.pwa_token
|
|
},
|
|
body: 'pwa_token=' + encodeURIComponent(auth.pwa_token)
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// CSRF-Token aktualisieren
|
|
auth.csrf_token = data.csrf_token;
|
|
this.saveAuth(auth);
|
|
}
|
|
return data;
|
|
});
|
|
},
|
|
|
|
// Logout
|
|
logout: function() {
|
|
this.clearAuth();
|
|
location.reload();
|
|
}
|
|
};
|
|
|
|
// UI Elements
|
|
const loadingOverlay = document.getElementById('loading-overlay');
|
|
const loginScreen = document.getElementById('login-screen');
|
|
const scannerApp = document.getElementById('scanner-app');
|
|
const loginForm = document.getElementById('login-form');
|
|
const loginError = document.getElementById('login-error');
|
|
const loginBtn = document.getElementById('login-btn');
|
|
const logoutBtn = document.getElementById('logout-btn');
|
|
const headerUsername = document.getElementById('header-username');
|
|
|
|
// Scanner Config - wird nach Login gesetzt
|
|
var SCANNER_CONFIG = null;
|
|
|
|
// App initialisieren
|
|
async function initApp() {
|
|
// Gespeicherte Auth pruefen
|
|
const auth = PWA_AUTH.getStoredAuth();
|
|
|
|
if (auth && auth.pwa_token) {
|
|
// Token verifizieren
|
|
try {
|
|
const result = await PWA_AUTH.verify();
|
|
if (result.success) {
|
|
showScanner(auth, result.csrf_token);
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
console.error('Verify error:', e);
|
|
}
|
|
}
|
|
|
|
// Login anzeigen
|
|
showLogin();
|
|
}
|
|
|
|
function showLogin() {
|
|
loadingOverlay.classList.add('hidden');
|
|
loginScreen.classList.remove('hidden');
|
|
scannerApp.classList.add('hidden');
|
|
document.getElementById('login-user').focus();
|
|
}
|
|
|
|
function showScanner(auth, csrfToken) {
|
|
loadingOverlay.classList.add('hidden');
|
|
loginScreen.classList.add('hidden');
|
|
scannerApp.classList.remove('hidden');
|
|
|
|
// Username anzeigen
|
|
const user = auth.user || {};
|
|
headerUsername.textContent = user.firstname ? (user.firstname + ' ' + user.lastname) : user.login;
|
|
|
|
// Scanner Config setzen
|
|
SCANNER_CONFIG = {
|
|
ajaxUrl: PWA_AUTH.ajaxUrl,
|
|
token: csrfToken || auth.csrf_token,
|
|
mode: '<?php echo $mode; ?>',
|
|
enableVibration: <?php echo getDolGlobalInt('HANDYBARCODESCANNER_ENABLE_VIBRATION', 1); ?>,
|
|
enableSound: <?php echo getDolGlobalInt('HANDYBARCODESCANNER_ENABLE_SOUND', 0); ?>,
|
|
lang: {
|
|
productNotFound: 'Produkt nicht gefunden',
|
|
added: 'Produkt eingestellt',
|
|
saved: 'Gespeichert',
|
|
error: 'Fehler',
|
|
selectSupplier: 'Lieferant waehlen',
|
|
noSupplier: 'Kein Lieferant fuer dieses Produkt hinterlegt',
|
|
quantity: 'Menge',
|
|
add: 'Hinzufuegen',
|
|
price: 'Preis',
|
|
stock: 'Lagerbestand',
|
|
currentStock: 'Aktueller Bestand',
|
|
newStock: 'Neuer Bestand',
|
|
save: 'Speichern',
|
|
confirmStockChange: 'Bestandsaenderung bestaetigen',
|
|
cancel: 'Abbrechen',
|
|
confirm: 'Bestaetigen',
|
|
openShop: 'Shop oeffnen',
|
|
cheapest: 'Guenstigster',
|
|
cameraError: 'Kamera-Zugriff fehlgeschlagen',
|
|
ref: 'Referenz',
|
|
product: 'Produkt',
|
|
supplierOrderRef: 'Lieferantenbestellnummer',
|
|
startScan: 'Scannen starten',
|
|
stopScan: 'Scannen stoppen'
|
|
}
|
|
};
|
|
|
|
// Scanner.js Config global setzen
|
|
window.SCANNER_CONFIG = SCANNER_CONFIG;
|
|
|
|
// Scanner.js initialisieren (mit kleiner Verzoegerung fuer DOM)
|
|
setTimeout(function() {
|
|
if (typeof window.initScanner === 'function') {
|
|
window.initScanner();
|
|
} else {
|
|
console.error('initScanner not found');
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
// Login Form Handler
|
|
loginForm.addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
|
|
const username = document.getElementById('login-user').value.trim();
|
|
const password = document.getElementById('login-pass').value;
|
|
const remember = document.getElementById('login-remember').checked;
|
|
|
|
if (!username || !password) {
|
|
loginError.textContent = 'Benutzername und Passwort erforderlich';
|
|
loginError.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
loginBtn.disabled = true;
|
|
loginBtn.textContent = 'Anmelden...';
|
|
loginError.style.display = 'none';
|
|
|
|
try {
|
|
const result = await PWA_AUTH.login(username, password, remember);
|
|
|
|
if (result.success) {
|
|
showScanner({
|
|
pwa_token: result.pwa_token,
|
|
csrf_token: result.csrf_token,
|
|
user: result.user,
|
|
expires: result.expires
|
|
}, result.csrf_token);
|
|
} else {
|
|
loginError.textContent = result.error || 'Login fehlgeschlagen';
|
|
loginError.style.display = 'block';
|
|
loginBtn.disabled = false;
|
|
loginBtn.textContent = 'Anmelden';
|
|
}
|
|
} catch (e) {
|
|
console.error('Login error:', e);
|
|
loginError.textContent = 'Verbindungsfehler';
|
|
loginError.style.display = 'block';
|
|
loginBtn.disabled = false;
|
|
loginBtn.textContent = 'Anmelden';
|
|
}
|
|
});
|
|
|
|
// Logout Handler
|
|
logoutBtn.addEventListener('click', function() {
|
|
PWA_AUTH.logout();
|
|
});
|
|
|
|
// Service Worker registrieren
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.register('sw.js').catch(function(err) {
|
|
console.log('ServiceWorker registration failed:', err);
|
|
});
|
|
}
|
|
|
|
// App starten
|
|
initApp();
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
|
|
<script src="<?php echo dol_buildpath('/handybarcodescanner/js/scanner.js', 1); ?>"></script>
|
|
</body>
|
|
</html>
|