Consulta produtos
Open
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 5.2k
- Forks
- 1.8k
- Avg merge
- 3d 12h
- Merged PRs (30d)
- 6
Description
<title>Consulta de Produtos - Código de Barras</title>
<style>
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
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>
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 expirouDigite 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>
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by opening the supplied standalone HTML document in a browser and reviewing its inline JavaScript entry point. The issue names no repository file, test, or specific change; clarify how this product lookup should integrate with the examples repository and define acceptance criteria before implementation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100