Skip to content

Consulta produtos #1491

@pedidosbarataomataapp

Description

@pedidosbarataomataapp
<title>Consulta de Produtos - Código de Barras</title> <style> * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
    body {
        font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
        background: #f0f2f5;
        margin: 0;
        padding: 16px;
        color: #1e293b;
    }

    .container {
        max-width: 700px;
        margin: 0 auto;
    }

    .card {
        background: white;
        border-radius: 28px;
        box-shadow: 0 8px 20px rgba(0,0,0,0.05), 0 2px 4px rgba(0,0,0,0.02);
        padding: 20px;
        margin-bottom: 20px;
        transition: all 0.2s;
    }

    .header {
        text-align: center;
        margin-bottom: 20px;
    }

    .header h1 {
        font-size: 1.7rem;
        font-weight: 600;
        margin: 0 0 4px 0;
        background: linear-gradient(135deg, #2b6e3c, #1e8e3e);
        background-clip: text;
        -webkit-background-clip: text;
        color: transparent;
    }

    .header p {
        font-size: 0.85rem;
        color: #5b6e8c;
        margin: 0;
    }

    .import-area {
        background: #f8fafc;
        border: 1.5px dashed #94a3b8;
        border-radius: 20px;
        padding: 16px;
        text-align: center;
        margin-bottom: 24px;
        transition: background 0.2s;
    }

    .import-label {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        background: #1e8e3e;
        color: white;
        padding: 10px 20px;
        border-radius: 40px;
        font-weight: 500;
        font-size: 0.9rem;
        cursor: pointer;
        transition: background 0.2s;
        box-shadow: 0 1px 2px rgba(0,0,0,0.05);
    }

    .import-label:active {
        background: #166b2e;
        transform: scale(0.97);
    }

    .import-label input {
        display: none;
    }

    .file-status {
        font-size: 0.8rem;
        margin-top: 10px;
        color: #2c6b2f;
        font-weight: 500;
    }

    .stats {
        display: flex;
        justify-content: space-between;
        background: #eef2ff;
        padding: 12px 16px;
        border-radius: 60px;
        margin-bottom: 24px;
        font-size: 0.85rem;
        font-weight: 500;
    }

    .search-box {
        display: flex;
        gap: 12px;
        align-items: center;
        flex-wrap: wrap;
        margin-bottom: 24px;
    }

    .search-input-wrapper {
        flex: 1;
        position: relative;
    }

    .search-input-wrapper input {
        width: 100%;
        padding: 16px 18px;
        font-size: 1rem;
        border: 1.5px solid #e2e8f0;
        border-radius: 48px;
        background: white;
        transition: all 0.2s;
        font-family: monospace;
        letter-spacing: 0.3px;
    }

    .search-input-wrapper input:focus {
        outline: none;
        border-color: #2b6e3c;
        box-shadow: 0 0 0 3px rgba(30,142,62,0.2);
    }

    .scan-btn {
        background: #1e293b;
        color: white;
        border: none;
        border-radius: 48px;
        padding: 0 22px;
        font-size: 1rem;
        font-weight: 500;
        cursor: pointer;
        display: flex;
        align-items: center;
        gap: 6px;
        transition: 0.1s linear;
        height: 54px;
    }

    .scan-btn:active {
        background: #0f172a;
        transform: scale(0.96);
    }

    .result-card {
        background: white;
        border-radius: 28px;
        padding: 20px;
        box-shadow: 0 4px 12px rgba(0,0,0,0.05);
    }

    .product-found {
        border-left: 6px solid #1e8e3e;
    }

    .product-notfound {
        border-left: 6px solid #dc2626;
        text-align: center;
        color: #7f8c8d;
    }

    .product-title {
        font-size: 1.5rem;
        font-weight: 700;
        margin-bottom: 8px;
        word-break: break-word;
    }

    .barcode {
        font-family: monospace;
        background: #f1f5f9;
        padding: 6px 12px;
        border-radius: 40px;
        display: inline-block;
        font-size: 0.85rem;
        margin-bottom: 16px;
    }

    .info-row {
        display: flex;
        justify-content: space-between;
        padding: 10px 0;
        border-bottom: 1px solid #edf2f7;
    }

    .info-label {
        font-weight: 600;
        color: #4a5b7a;
    }

    .info-value {
        font-weight: 500;
        text-align: right;
        word-break: break-word;
        max-width: 60%;
    }

    .price {
        font-size: 1.4rem;
        font-weight: 800;
        color: #2b6e3c;
    }

    .help-text {
        font-size: 0.75rem;
        text-align: center;
        margin-top: 24px;
        color: #6c7a91;
    }

    /* Timer bar */
    .timer-container {
        margin-top: 16px;
        padding: 8px 12px;
        background: #f8fafc;
        border-radius: 60px;
        display: flex;
        align-items: center;
        gap: 12px;
        font-size: 0.8rem;
    }

    .timer-bar {
        flex: 1;
        height: 6px;
        background: #e2e8f0;
        border-radius: 10px;
        overflow: hidden;
    }

    .timer-progress {
        width: 0%;
        height: 100%;
        background: #1e8e3e;
        border-radius: 10px;
        transition: width 0.1s linear;
    }

    .timer-text {
        font-weight: 500;
        color: #475569;
        min-width: 65px;
        text-align: right;
    }

    button, .import-label {
        cursor: pointer;
        user-select: none;
    }

    @media (max-width: 480px) {
        body {
            padding: 12px;
        }
        .search-box {
            flex-direction: column;
            gap: 8px;
        }
        .scan-btn {
            width: 100%;
            justify-content: center;
        }
        .info-row {
            flex-direction: column;
            gap: 4px;
        }
        .info-value {
            text-align: left;
            max-width: 100%;
        }
        .timer-container {
            font-size: 0.7rem;
        }
    }
</style>

📦 Leitor de Estoque

consulta por código de barras (EAN-13 / GTIN)

    <div class="import-area" id="importArea">
        <label class="import-label">
            📂 IMPORTAR CADASTRO
            <input type="file" id="fileInput" accept=".html,.htm,.txt,.csv,text/html,text/plain">
        </label>
        <div id="fileStatus" class="file-status">Nenhum arquivo carregado. Clique para importar o relatório.</div>
        <div id="recordCount" style="font-size:12px; margin-top:6px; color:#475569;"></div>
    </div>

    <div class="stats" id="statsPanel">
        <span>📋 Produtos: <strong id="totalProducts">0</strong></span>
        <span>✅ Consultas: <strong id="queryCount">0</strong></span>
    </div>

    <div class="search-box">
        <div class="search-input-wrapper">
            <input type="text" id="barcodeInput" placeholder="🔍 Digite ou cole o código de barras" autocomplete="off" enterkeyhint="search">
        </div>
        <button class="scan-btn" id="searchBtn">
            🔎 Consultar
        </button>
    </div>

    <!-- Timer de limpeza automática -->
    <div class="timer-container" id="timerContainer" style="display: none;">
        <span>🕐 Limpeza automática:</span>
        <div class="timer-bar">
            <div class="timer-progress" id="timerProgress"></div>
        </div>
        <span class="timer-text" id="timerText">5.0s</span>
    </div>

    <div class="result-card" id="resultArea">
        <div style="text-align: center; color: #94a3b8; padding: 16px;">
            ⚡ Aguardando consulta<br>
            <span style="font-size: 13px;">Carregue o arquivo e pesquise por código EAN</span>
        </div>
    </div>
    <div class="help-text">
        💡 Dica: Use o arquivo "RELATORIO FISCAL + VALORES.html". <br>
        Após cada consulta, o resultado e o campo são limpos automaticamente em 5 segundos.
    </div>
</div>
<script> // -------------------------------------------------------------- // Estrutura: produtos em Map (chave = EAN-13) // -------------------------------------------------------------- let productMap = new Map(); let totalImported = 0; let queryCounter = 0; // Controle do timer let clearTimer = null; let timerInterval = null; let remainingTime = 0; let isTimerRunning = false; // Elementos DOM const fileInput = document.getElementById('fileInput'); const fileStatusDiv = document.getElementById('fileStatus'); const recordCountSpan = document.getElementById('recordCount'); const totalProductsSpan = document.getElementById('totalProducts'); const queryCountSpan = document.getElementById('queryCount'); const barcodeInput = document.getElementById('barcodeInput'); const searchBtn = document.getElementById('searchBtn'); const resultArea = document.getElementById('resultArea'); const timerContainer = document.getElementById('timerContainer'); const timerProgress = document.getElementById('timerProgress'); const timerText = document.getElementById('timerText'); // Atualiza contadores na tela function updateStatsUI() { totalProductsSpan.innerText = productMap.size; queryCountSpan.innerText = queryCounter; recordCountSpan.innerText = productMap.size > 0 ? `✅ ${productMap.size} produtos indexados` : '⚡ Nenhum produto carregado'; if (productMap.size === 0) { fileStatusDiv.innerText = 'Nenhum arquivo carregado. Clique para importar o relatório.'; fileStatusDiv.style.color = '#b91c1c'; } else { fileStatusDiv.innerText = `📁 Arquivo carregado com sucesso (${productMap.size} registros)`; fileStatusDiv.style.color = '#2c6b2f'; } } // Para o timer de limpeza function stopClearTimer() { if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } if (clearTimer) { clearTimeout(clearTimer); clearTimer = null; } isTimerRunning = false; timerContainer.style.display = 'none'; } // Inicia timer de 5 segundos para limpar o campo e resultado function startClearTimer() { // Para qualquer timer anterior stopClearTimer(); remainingTime = 5.0; timerContainer.style.display = 'flex'; timerProgress.style.width = '100%'; timerText.innerText = '5.0s'; isTimerRunning = true; // Atualiza barra a cada 100ms timerInterval = setInterval(() => { if (remainingTime > 0) { remainingTime -= 0.1; const percent = (remainingTime / 5.0) * 100; timerProgress.style.width = `${percent}%`; timerText.innerText = `${remainingTime.toFixed(1)}s`; } }, 100); // Timer final para limpar clearTimer = setTimeout(() => { // Limpa o campo de busca barcodeInput.value = ''; // Restaura o resultado para estado inicial if (productMap.size > 0) { resultArea.innerHTML = `
🔄 Pesquisa expirou
Digite um novo código de barras
`; } else { resultArea.innerHTML = `
⚡ Aguardando consulta
Carregue o arquivo e pesquise por código EAN
`; } // Foca no campo para nova consulta barcodeInput.focus(); stopClearTimer(); }, 5000); } // Extrai preço de custo (converte vírgula para ponto) function parsePrice(priceStr) { if (!priceStr) return 0; let cleaned = priceStr.toString().trim().replace(',', '.'); let num = parseFloat(cleaned); return isNaN(num) ? 0 : num; } function formatMoney(value) { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); } function normalizeBarcode(raw) { if (!raw) return ''; return raw.toString().trim().replace(/\s+/g, '').replace(/-/g, ''); } function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }); } function showResult(product, searchedCode) { if (!product) { resultArea.innerHTML = `
🔍❌
Código não encontrado
🔢 ${escapeHtml(searchedCode)}
Verifique o código ou recarregue o arquivo.
`; return; } const custoFormatado = formatMoney(product.precoCusto); resultArea.innerHTML = `
${escapeHtml(product.descricao)}
📌 Código: ${escapeHtml(product.ean)}
💰 PREÇO DE CUSTO ${custoFormatado}
🆔 ID do produto ${product.id}
📦 Unidade ${escapeHtml(product.medida || '—')}
🏷️ NCM ${escapeHtml(product.ncm || '—')}
⚖️ Situação Tributária ${escapeHtml(product.situacaoTributaria || '—')}
`; } // PARSER DO ARQUIVO HTML function parseHtmlFileAndIndex(htmlString) { const parser = new DOMParser(); const doc = parser.parseFromString(htmlString, 'text/html'); const tabela = doc.querySelector('table.tabela'); if (!tabela) { throw new Error('Arquivo inválido: tabela com classe "tabela" não encontrada.'); } const rows = tabela.querySelectorAll('tr'); if (rows.length < 2) { throw new Error('Arquivo sem dados (sem linhas de produtos).'); } let columnMap = { id: -1, ean: -1, descricao: -1, precoCusto: -1, medida: -1, ncm: -1, situacaoTributaria: -1 }; for (let i = 0; i < rows.length; i++) { const ths = rows[i].querySelectorAll('th'); if (ths.length > 0) { for (let idx = 0; idx < ths.length; idx++) { const text = ths[idx].innerText.trim().toUpperCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ''); if (text === 'ID') columnMap.id = idx; else if (text === 'EAN-13') columnMap.ean = idx; else if (text === 'DESCRICAO') columnMap.descricao = idx; else if (text === 'PRECO CUSTO') columnMap.precoCusto = idx; else if (text === 'MEDIDA') columnMap.medida = idx; else if (text === 'NCM') columnMap.ncm = idx; else if (text === 'SITUACAO TRIBUTARIA') columnMap.situacaoTributaria = idx; } break; } } if (columnMap.id === -1 || columnMap.ean === -1 || columnMap.descricao === -1 || columnMap.precoCusto === -1) { throw new Error('Colunas obrigatórias (ID, EAN-13, DESCRICAO, PRECO CUSTO) não encontradas.'); } const newMap = new Map(); for (let i = 0; i < rows.length; i++) { const tds = rows[i].querySelectorAll('td'); if (tds.length === 0) continue; const idCell = tds[columnMap.id]?.innerText.trim(); if (!idCell || isNaN(parseInt(idCell))) continue; const eanRaw = tds[columnMap.ean]?.innerText.trim() || ''; let eanCode = normalizeBarcode(eanRaw); if (eanCode === '') continue; const descricao = tds[columnMap.descricao]?.innerText.trim() || 'SEM DESCRIÇÃO'; const precoRaw = tds[columnMap.precoCusto]?.innerText.trim().replace('R$', '').replace(',', '.').trim(); let precoCusto = parsePrice(precoRaw); const medida = tds[columnMap.medida]?.innerText.trim() || ''; const ncm = tds[columnMap.ncm]?.innerText.trim() || ''; const situacaoTributaria = tds[columnMap.situacaoTributaria]?.innerText.trim() || ''; newMap.set(eanCode, { id: idCell, ean: eanCode, descricao: descricao, precoCusto: precoCusto, medida: medida, ncm: ncm, situacaoTributaria: situacaoTributaria }); } if (newMap.size === 0) { throw new Error('Nenhum produto válido com código de barras encontrado.'); } return newMap; } function loadFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function(e) { try { const map = parseHtmlFileAndIndex(e.target.result); resolve(map); } catch (err) { reject(err); } }; reader.onerror = () => reject(new Error('Erro ao ler o arquivo.')); reader.readAsText(file, 'UTF-8'); }); } fileInput.addEventListener('change', async (event) => { const file = event.target.files[0]; if (!file) return; fileStatusDiv.innerText = '⏳ Processando arquivo... Aguarde.'; fileStatusDiv.style.color = '#a16207'; try { const newMap = await loadFile(file); productMap = newMap; updateStatsUI(); // Para qualquer timer pendente stopClearTimer(); resultArea.innerHTML = `
✅ Arquivo carregado! ${productMap.size} produtos prontos.
🔎 Digite um código de barras para consultar.
`; barcodeInput.value = ''; barcodeInput.focus(); } catch (error) { fileStatusDiv.innerText = `❌ Erro: ${error.message}`; fileStatusDiv.style.color = '#b91c1c'; resultArea.innerHTML = `
Falha ao processar arquivo.
${escapeHtml(error.message)}
`; productMap.clear(); updateStatsUI(); } finally { fileInput.value = ''; } }); function performSearch() { let rawCode = barcodeInput.value.trim(); if (rawCode === "") { resultArea.innerHTML = `
⚠️ Digite ou cole um código de barras.
`; stopClearTimer(); return; } // Para timer anterior e inicia novo stopClearTimer(); queryCounter++; updateStatsUI(); const normalized = normalizeBarcode(rawCode); if (normalized === "") { showResult(null, rawCode); startClearTimer(); return; } const found = productMap.get(normalized); if (found) { showResult(found, normalized); } else { let altKey = normalized.replace(/^0+/, ''); let altFound = altKey !== normalized ? productMap.get(altKey) : null; if (altFound) { showResult(altFound, normalized); } else { showResult(null, rawCode); } } // Inicia timer para limpar após 5 segundos startClearTimer(); } searchBtn.addEventListener('click', performSearch); barcodeInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); performSearch(); } }); // Scanner com câmera const cameraBtn = document.createElement('button'); cameraBtn.innerText = '📷 Câmera'; cameraBtn.className = 'scan-btn'; cameraBtn.style.background = '#3b5249'; cameraBtn.style.padding = '0 16px'; cameraBtn.style.fontSize = '0.9rem'; cameraBtn.id = 'customCameraBtn'; const searchWrapper = document.querySelector('.search-box'); if (searchWrapper && !document.getElementById('customCameraBtn')) { searchWrapper.appendChild(cameraBtn); cameraBtn.addEventListener('click', async () => { if ('BarcodeDetector' in window) { try { const detector = new BarcodeDetector({ formats: ['ean_13', 'ean_8', 'code_128', 'code_39', 'upc_a', 'upc_e'] }); const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); const video = document.createElement('video'); video.srcObject = stream; video.setAttribute('playsinline', true); video.style.position = 'fixed'; video.style.top = '0'; video.style.left = '0'; video.style.width = '100%'; video.style.height = '100%'; video.style.objectFit = 'cover'; video.style.zIndex = '9999'; video.style.background = 'black'; document.body.appendChild(video); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); video.play(); const closeBtn = document.createElement('button'); closeBtn.innerText = '✖ Fechar Scanner'; closeBtn.style.position = 'fixed'; closeBtn.style.bottom = '30px'; closeBtn.style.left = '50%'; closeBtn.style.transform = 'translateX(-50%)'; closeBtn.style.zIndex = '10000'; closeBtn.style.padding = '12px 24px'; closeBtn.style.backgroundColor = '#dc2626'; closeBtn.style.color = 'white'; closeBtn.style.border = 'none'; closeBtn.style.borderRadius = '60px'; closeBtn.style.fontWeight = 'bold'; document.body.appendChild(closeBtn); let scanning = true; const scanInterval = setInterval(async () => { if (!scanning) return; if (video.videoWidth === 0) return; canvas.width = video.videoWidth; canvas.height = video.videoHeight; context.drawImage(video, 0, 0, canvas.width, canvas.height); const imageData = context.getImageData(0, 0, canvas.width, canvas.height); try { const barcodes = await detector.detect(imageData); if (barcodes.length > 0) { const code = barcodes[0].rawValue; scanning = false; clearInterval(scanInterval); stream.getTracks().forEach(track => track.stop()); video.remove(); closeBtn.remove(); barcodeInput.value = code; performSearch(); } } catch (err) {} }, 300); closeBtn.addEventListener('click', () => { scanning = false; clearInterval(scanInterval); stream.getTracks().forEach(track => track.stop()); video.remove(); closeBtn.remove(); }); } catch (err) { alert('Erro ao acessar câmera: ' + err.message); } } else { alert('Seu navegador não suporta leitura por câmera. Digite o código manualmente.'); } }); } updateStatsUI(); if (productMap.size === 0) { resultArea.innerHTML = `
📂
Clique em "IMPORTAR CADASTRO" e selecione o arquivo
RELATORIO FISCAL + VALORES.html
Após carregar, consulte produtos por código de barras.
`; } </script>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions