diff --git a/app.js b/app.js index a3d10bc..bd2740c 100644 --- a/app.js +++ b/app.js @@ -3,10 +3,39 @@ const defaultConfig = { key: 'sb_publishable_rIcKdaflOvJ0DLTJDcOrxA_bpTGG2hA' }; + +function storageGet(key, fallback = null) { + try { + const value = window.localStorage.getItem(key); + return value === null ? fallback : value; + } catch (_) { + return fallback; + } +} + +function storageSet(key, value) { + try { + window.localStorage.setItem(key, value); + return true; + } catch (_) { + return false; + } +} + +function storageGetJSON(key, fallback) { + const raw = storageGet(key, null); + if (!raw) return fallback; + try { + return JSON.parse(raw); + } catch (_) { + return fallback; + } +} + + const el = { - supabaseUrl: document.getElementById('supabaseUrl'), - supabaseKey: document.getElementById('supabaseKey'), - connectBtn: document.getElementById('connectBtn'), + paleteIncompletoToggle: document.getElementById('paleteIncompletoToggle'), + paleteIncompletoFields: document.getElementById('paleteIncompletoFields'), connectionStatus: document.getElementById('connectionStatus'), feedback: document.getElementById('feedback'), estoqueForm: document.getElementById('estoqueForm'), @@ -14,9 +43,58 @@ const el = { expedicaoForm: document.getElementById('expedicaoForm'), importForm: document.getElementById('importForm'), importFile: document.getElementById('importFile'), + importSyncMode: document.getElementById('importSyncMode'), + importFileName: document.getElementById('importFileName'), + selectImportBtn: document.getElementById('selectImportBtn'), + adminPasteForm: document.getElementById('adminPasteForm'), + adminPasteInput: document.getElementById('adminPasteInput'), + adminPasteSyncMode: document.getElementById('adminPasteSyncMode'), estoqueTableBody: document.querySelector('#estoqueTable tbody'), consultaAreaBody: document.querySelector('#consultaAreaTable tbody'), + consultaChaoBody: document.querySelector('#consultaChaoTable tbody'), + consultaSkuAreaBody: document.querySelector('#consultaSkuAreaTable tbody'), + consultaFilterForm: document.getElementById('consultaFilterForm'), + consultaDepositoFilter: document.getElementById('consultaDepositoFilter'), + consultaSideFilter: document.getElementById('consultaSideFilter'), + consultaMapaBox: document.getElementById('consultaMapaBox'), + mapaAreaSelect: document.getElementById('mapaAreaSelect'), + mapaPosicaoInput: document.getElementById('mapaPosicaoInput'), + exportConsultaResumoPdfBtn: document.getElementById('exportConsultaResumoPdfBtn'), totaisSkuBody: document.querySelector('#totaisSkuTable tbody'), + sobrasB01Body: document.querySelector('#sobrasB01Table tbody'), + planejamentoForm: document.getElementById('planejamentoForm'), + previsaoEntrada: document.getElementById('previsaoEntrada'), + planejamentoFile: document.getElementById('planejamentoFile'), + planejamentoResultado: document.getElementById('planejamentoResultado'), + planejamentoBody: document.querySelector('#planejamentoTable tbody'), + ocupacaoForm: document.getElementById('ocupacaoForm'), + ocupacaoStatus: document.getElementById('ocupacaoStatus'), + exportOcupacaoPdfBtn: document.getElementById('exportOcupacaoPdfBtn'), + ocupacaoBody: document.querySelector('#ocupacaoTable tbody'), + g1DetalheBody: document.querySelector('#g1DetalheTable tbody'), + g1TotaisStatus: document.getElementById('g1TotaisStatus'), + contagemForm: document.getElementById('contagemForm'), + contagemScope: document.getElementById('contagemScope'), + contagemSideLabel: document.getElementById('contagemSideLabel'), + contagemSide: document.getElementById('contagemSide'), + contagemLoadBtn: document.getElementById('contagemLoadBtn'), + contagemImportBtn: document.getElementById('contagemImportBtn'), + contagemEstimateBtn: document.getElementById('contagemEstimateBtn'), + contagemClearChecksBtn: document.getElementById('contagemClearChecksBtn'), + contagemApplyBtn: document.getElementById('contagemApplyBtn'), + contagemFile: document.getElementById('contagemFile'), + contagemImportResetToggle: document.getElementById('contagemImportResetToggle'), + contagemExportBtn: document.getElementById('contagemExportBtn'), + contagemStatus: document.getElementById('contagemStatus'), + contagemBody: document.querySelector('#contagemTable tbody'), + contagemResumoBody: document.querySelector('#contagemResumoTable tbody'), + turnoForm: document.getElementById('turnoForm'), + turnoFile: document.getElementById('turnoFile'), + turnoStatus: document.getElementById('turnoStatus'), + turnoSkuBody: document.querySelector('#turnoSkuTable tbody'), + exportTurnoExcelBtn: document.getElementById('exportTurnoExcelBtn'), + exportTurnoPdfBtn: document.getElementById('exportTurnoPdfBtn'), + turnoHistoryBody: document.querySelector('#turnoHistoryTable tbody'), movimentacoesBody: document.querySelector('#movimentacoesTable tbody'), exportCadastroBtn: document.getElementById('exportCadastroBtn'), exportConsultaBtn: document.getElementById('exportConsultaBtn'), @@ -26,20 +104,70 @@ const el = { visualizarLayoutBtn: document.getElementById('visualizarLayoutBtn'), layoutContainer: document.getElementById('layoutContainer'), layoutGrid: document.getElementById('layoutGrid'), + estruturasGrid: document.getElementById('estruturasGrid'), + semanticLayout: document.getElementById('semanticLayout'), exportLayoutPdfBtn: document.getElementById('exportLayoutPdfBtn'), - fecharLayoutBtn: document.getElementById('fecharLayoutBtn') + fecharLayoutBtn: document.getElementById('fecharLayoutBtn'), + themeToggleBtn: document.getElementById('themeToggleBtn') }; let supabaseClient; -let cache = { estoque: [], movimentacoes: [] }; +let cache = { estoque: [], movimentacoes: [], produtos: [] }; +let fracionadoMap = {}; +let turnoSnapshots = storageGetJSON('wmss_turno_snapshots', []); +let lastTurnoResultado = []; +let turnoUltimaPlanilhaSku = storageGetJSON('wmss_turno_ultima_planilha_sku', {}); +let contagemMap = storageGetJSON('wmss_contagem_map', {}); + +const manualOcupados = new Set([ + 'A14', 'A15', 'A16', 'A17', 'A18', 'A19', + 'B21D', 'B20D', 'B22D', 'B20E', + 'C15', 'C14', 'C12', 'C11', 'C10', 'C09', 'C08', + 'A09', 'A08', 'A07', 'A06', 'A05', 'A04', 'A03' +]); + +const capacidadeGalpoes = { + principal: 2520, + tissue: 1728, + lonil: 1112, + ttd: 0 +}; + +const ocupacaoRules = { + larguraPl2: 6, + profundidade: { A: 5, BD: 5, BE: 4, C: 6 }, + posicoesPorRua: 22, + interditados: { A: 1, BD: 1, BE: 1, C: 1 } +}; -el.supabaseUrl.value = defaultConfig.url; -el.supabaseKey.value = defaultConfig.key; +const g1Modelo = [ + { bloco: 'A', posicoes: 22, palletPosicao: 40, bloqueado: 80, terceiros: 400 }, + { bloco: 'B', posicoes: 22, palletPosicao: 40, bloqueado: 30, terceiros: 0 }, + { bloco: 'C', posicoes: 22, palletPosicao: 32, bloqueado: 16, terceiros: 0 }, + { bloco: 'D', posicoes: 22, palletPosicao: 48, bloqueado: 20, terceiros: 412 }, + { bloco: 'H1', posicoes: 12, palletPosicao: 40, bloqueado: 40, terceiros: 120 }, + { bloco: 'H2', posicoes: 2, palletPosicao: 32, bloqueado: 16, terceiros: 0 }, + { bloco: 'H3', posicoes: 4, palletPosicao: 48, bloqueado: 16, terceiros: 0 }, + { bloco: 'PP', posicoes: 1, palletPosicao: 364, bloqueado: 0, terceiros: 53 } +]; + +const capacidadePlanejamento = { + A: 80, + BD: 40, + BE: 32, + C: 48 +}; -const autoExportEnabled = localStorage.getItem('wmss_auto_export') === '1'; +const autoExportEnabled = storageGet('wmss_auto_export', '0') === '1'; if (el.autoExportToggle) el.autoExportToggle.checked = autoExportEnabled; +fracionadoMap = storageGetJSON('wmss_fracionado_map', {}); + +const darkModeEnabled = storageGet('wmss_theme', 'light') === 'dark'; +document.body.classList.toggle('dark', darkModeEnabled); +if (el.themeToggleBtn) el.themeToggleBtn.textContent = darkModeEnabled ? '☀️ Modo claro' : '🌙 Modo escuro'; function setStatus(target, message, type = '') { + if (!target) return; target.textContent = message; target.className = `status ${type}`.trim(); } @@ -52,21 +180,141 @@ function normalizeText(value) { return String(value ?? '').trim().toUpperCase(); } +function normalizeAreaCode(value) { + return normalizeText(value).replace(/[-_\s]/g, ''); +} + +function estoqueKey(area, sku, tipo) { + return `${normalizeAreaCode(area)}|${Number(sku)}|${normalizeText(tipo)}`; +} + +function getFardosPorPalete(sku) { + const produto = cache.produtos.find((item) => Number(item.sku) === Number(sku)); + const valor = Number(produto?.fardos_por_palete); + return Number.isFinite(valor) && valor > 0 ? valor : null; +} + function shouldDeleteByAction(actionValue) { const action = normalizeText(actionValue); return ['APAGAR', 'EXCLUIR', 'DELETE', 'DEL', 'REMOVER', 'REMOVE'].includes(action); } +function isRetrabalhoArea(area) { + return normalizeAreaCode(area) === 'C01'; +} + +function parseIncomingForecast(text) { + return String(text || '') + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .map((line) => { + const [skuRaw, paletesRaw] = line.split(',').map((part) => part?.trim()); + return { sku: Number(skuRaw), paletes: Number(paletesRaw) }; + }) + .filter((item) => Number.isFinite(item.sku) && Number.isFinite(item.paletes) && item.paletes > 0); +} + +function parseIncomingForecastRows(rows) { + return rows + .map((row) => { + const normalized = Object.fromEntries( + Object.entries(row).map(([k, v]) => [String(k).trim().toLowerCase(), v]) + ); + return { + sku: Number(normalized.sku), + paletes: Number(normalized.paletes) + }; + }) + .filter((item) => Number.isFinite(item.sku) && Number.isFinite(item.paletes) && item.paletes > 0); +} + +function exportTurnoResultadoExcel() { + if (!lastTurnoResultado.length) return showFeedback('Execute a conferência do turno antes de exportar.', 'error'); + exportWorkbook('resultado_turno.xlsx', [{ name: 'Saida_Turno', data: lastTurnoResultado }]); +} + +function exportTurnoResultadoPDF() { + if (!lastTurnoResultado.length) return showFeedback('Execute a conferência do turno antes de exportar.', 'error'); + if (!window.jspdf) return showFeedback('Biblioteca de PDF não carregada.', 'error'); + + const { jsPDF } = window.jspdf; + const pdf = new jsPDF('portrait'); + let y = 15; + pdf.text('Resultado da Conferência de Turno', 10, y); + y += 8; + lastTurnoResultado.forEach((row) => { + pdf.text(`SKU ${row.sku} | ${row.tipo} | Paletes: ${row.paletes_sairam} | Fardos: ${row.fardos_estimados}`, 10, y); + y += 6; + if (y > 280) { + pdf.addPage(); + y = 15; + } + }); + pdf.save('resultado_turno.pdf'); +} + +function mapAreaToPlanejamentoBloco(area) { + const normalized = normalizeAreaCode(area); + if (/^A\d+$/.test(normalized)) return 'A'; + if (/^B\d+D$/.test(normalized) || /^BD\d+$/.test(normalized)) return 'BD'; + if (/^B\d+E$/.test(normalized) || /^BE\d+$/.test(normalized)) return 'BE'; + if (/^C\d+$/.test(normalized)) return 'C'; + return null; +} + +function getPlanejamentoOcupacaoAtual() { + const ocupado = { A: 0, BD: 0, BE: 0, C: 0 }; + cache.estoque.forEach((row) => { + const bloco = mapAreaToPlanejamentoBloco(row.area); + if (!bloco || isRetrabalhoArea(row.area)) return; + ocupado[bloco] += Number(row.paletes || 0); + }); + return ocupado; +} + +function renderPlanejamentoTable(ocupado, previsaoPaletes = 0) { + if (!el.planejamentoBody) return; + el.planejamentoBody.innerHTML = ''; + + const ordem = ['A', 'BD', 'BE', 'C']; + const totalLivre = ordem.reduce((acc, bloco) => acc + Math.max(0, capacidadePlanejamento[bloco] - (ocupado[bloco] || 0)), 0); + let restante = previsaoPaletes; + + ordem.forEach((bloco) => { + const cap = capacidadePlanejamento[bloco]; + const ocup = ocupado[bloco] || 0; + const livre = Math.max(0, cap - ocup); + const alocar = Math.min(restante, livre); + restante -= alocar; + const apos = ocup + alocar; + + const tr = document.createElement('tr'); + tr.innerHTML = `${bloco}${cap}${ocup}${livre}${apos}${restante >= 0 && alocar === 0 && previsaoPaletes > 0 ? 'Considerar desfazer blocado' : 'OK'}`; + el.planejamentoBody.appendChild(tr); + }); + + if (el.planejamentoResultado) { + if (!previsaoPaletes) { + setStatus(el.planejamentoResultado, `Capacidade livre total hoje: ${totalLivre} paletes.`, 'success'); + } else if (restante > 0) { + setStatus(el.planejamentoResultado, `Faltam ${restante} paletes de espaço. Sugestão: desfazer blocados para abrir capacidade.`, 'error'); + } else { + setStatus(el.planejamentoResultado, `Previsão comportada. Entrada prevista: ${previsaoPaletes} paletes.`, 'success'); + } + } +} + function createClient() { - const url = el.supabaseUrl.value.trim(); - const key = el.supabaseKey.value.trim(); - if (!url || !key) { - setStatus(el.connectionStatus, 'Informe URL e chave para conectar.', 'error'); - return; + try { + if (!window.supabase?.createClient) throw new Error('Biblioteca do Supabase indisponível no momento.'); + supabaseClient = window.supabase.createClient(defaultConfig.url, defaultConfig.key); + setStatus(el.connectionStatus, 'Conectado ao Supabase.', 'success'); + loadAll(); + } catch (error) { + setStatus(el.connectionStatus, 'Falha de conexão com Supabase.', 'error'); + showFeedback(`Erro ao conectar no Supabase: ${error.message}`, 'error'); } - supabaseClient = window.supabase.createClient(url, key); - setStatus(el.connectionStatus, 'Conectado ao Supabase.', 'success'); - loadAll(); } async function loadEstoque() { @@ -94,10 +342,19 @@ async function loadMovimentacoes() { renderMovimentacoes(); } +async function loadProdutos() { + const { data, error } = await supabaseClient + .from('produtos') + .select('sku, fardos_por_palete'); + + if (error) throw error; + cache.produtos = data ?? []; +} + async function loadAll() { if (!supabaseClient) return; try { - await Promise.all([loadEstoque(), loadMovimentacoes()]); + await Promise.all([loadEstoque(), loadMovimentacoes(), loadProdutos()]); showFeedback('Dados carregados com sucesso.'); } catch (error) { showFeedback(`Erro ao carregar dados: ${error.message}`, 'error'); @@ -149,8 +406,11 @@ function renderEstoque() { }); } -function groupTotalBySku(rows) { - const totals = rows.reduce((acc, row) => { +function groupTotalBySku(rows, options = {}) { + const { excludeRetrabalho = false } = options; + const filteredRows = excludeRetrabalho ? rows.filter((row) => !isRetrabalhoArea(row.area)) : rows; + + const totals = filteredRows.reduce((acc, row) => { acc[row.sku] = (acc[row.sku] || 0) + Number(row.paletes); return acc; }, {}); @@ -160,24 +420,351 @@ function groupTotalBySku(rows) { .sort((a, b) => a.sku - b.sku); } +function renderSobrasB01() { + if (!el.sobrasB01Body) return; + el.sobrasB01Body.innerHTML = ''; + + const sobras = cache.estoque.filter((row) => ['B01', 'B01E', 'B01D'].includes(normalizeAreaCode(row.area))); + + sobras.forEach((row) => { + const tr = document.createElement('tr'); + tr.innerHTML = `${row.area}${row.sku}${row.tipo}${row.paletes}`; + el.sobrasB01Body.appendChild(tr); + }); + + if (!sobras.length) { + const tr = document.createElement('tr'); + tr.innerHTML = 'Sem sobras cadastradas em B01/B01E/B01D.'; + el.sobrasB01Body.appendChild(tr); + } +} + +function renderSemanticLayout() { + if (!el.semanticLayout) return; + + el.semanticLayout.innerHTML = ` +
+
TISSUE
+
Rua de acesso
+
LONIL
+
+
+
C (12 posições)
+
BE / BD (12 posições)
+
A (12 posições)
+
+ Extintores + Escritório + Banheiro +
+
+
+ Obs.: B01 (E/D) = área de sobras | C01 = retrabalho (fora do total). +
+ `; +} + +function getDepositoFromArea(area) { + const a = normalizeAreaCode(area); + if (a.startsWith('TISSUE')) return 'TISSUE'; + if (a.startsWith('TTD')) return 'TTD'; + if (a.startsWith('LONIL')) return 'LONIL'; + if (/^(R\d+\.\d+|CHAOESTRUTURA)$/.test(a)) return 'ESTRUTURA'; + return 'PRINCIPAL'; +} + +function getSideFromArea(area) { + const a = normalizeAreaCode(area); + if (a.endsWith('D')) return 'D'; + if (a.endsWith('E')) return 'E'; + return 'ALL'; +} + +function sideLabel(side) { + if (side === 'D') return '→ Direita'; + if (side === 'E') return '← Esquerda'; + return '—'; +} + +function filterConsultaRows(rows) { + const deposito = normalizeText(el.consultaDepositoFilter?.value || 'ALL'); + const side = normalizeText(el.consultaSideFilter?.value || 'ALL'); + return rows.filter((row) => { + const dep = getDepositoFromArea(row.area); + const rowSide = getSideFromArea(row.area); + const depositoOk = deposito === 'ALL' ? true : dep === deposito; + const sideOk = side === 'ALL' ? true : rowSide === side; + return depositoOk && sideOk; + }); +} + +function renderConsultaSkuArea(rowsFiltrados) { + if (!el.consultaSkuAreaBody) return; + const grouped = {}; + rowsFiltrados.forEach((row) => { + const dep = getDepositoFromArea(row.area); + const key = `${dep}|${row.sku}`; + if (!grouped[key]) grouped[key] = { dep, sku: row.sku, paletes: 0, fardos: 0, missingFpp: false }; + grouped[key].paletes += Number(row.paletes || 0); + const fpp = getFardosPorPalete(row.sku); + if (fpp) grouped[key].fardos += Number(row.paletes || 0) * fpp; + else grouped[key].missingFpp = true; + }); + el.consultaSkuAreaBody.innerHTML = ''; + Object.values(grouped) + .sort((a, b) => a.dep.localeCompare(b.dep) || Number(a.sku) - Number(b.sku)) + .forEach((item) => { + const tr = document.createElement('tr'); + tr.innerHTML = `${item.dep}${item.sku}${item.paletes}${item.missingFpp ? 'N/D' : item.fardos}`; + el.consultaSkuAreaBody.appendChild(tr); + }); +} + +function renderConsultaChao(rowsFiltrados) { + if (!el.consultaChaoBody) return; + el.consultaChaoBody.innerHTML = ''; + const chaoRows = rowsFiltrados.filter((row) => normalizeAreaCode(row.area).includes('CHAO')); + chaoRows.forEach((row) => { + const dep = getDepositoFromArea(row.area); + const tr = document.createElement('tr'); + tr.innerHTML = `Chão ${dep}${sideLabel(getSideFromArea(row.area))}${row.area}${row.sku}${row.paletes}`; + el.consultaChaoBody.appendChild(tr); + }); + if (!chaoRows.length) { + const tr = document.createElement('tr'); + tr.innerHTML = 'Sem registros de chão no filtro atual.'; + el.consultaChaoBody.appendChild(tr); + } +} + +function renderConsultaMapaHint() { + if (!el.consultaMapaBox) return; + const area = normalizeText(el.mapaAreaSelect?.value || 'PRINCIPAL'); + const pos = normalizeAreaCode(el.mapaPosicaoInput?.value || ''); + const side = sideLabel(getSideFromArea(pos)); + const deposito = area === 'TTD' ? 'TDD' : area; + el.consultaMapaBox.textContent = pos + ? `Você está aqui: ${pos} | Depósito: ${deposito} | Lado: ${side}. Siga para o blocado indicado e confirme o endereço no sentido da seta.` + : `Selecione área e informe uma posição para orientação visual (ex.: B02D → Direita, B02E ← Esquerda).`; +} + function renderConsulta() { + const rowsFiltrados = filterConsultaRows(cache.estoque); el.consultaAreaBody.innerHTML = ''; - cache.estoque.forEach((row) => { + rowsFiltrados.forEach((row) => { const tr = document.createElement('tr'); - tr.innerHTML = `${row.area}${row.sku}${row.tipo}${row.paletes}`; + tr.innerHTML = `${getDepositoFromArea(row.area)}${sideLabel(getSideFromArea(row.area))}${row.area}${row.sku}${row.tipo}${row.paletes}`; el.consultaAreaBody.appendChild(tr); }); - const totais = groupTotalBySku(cache.estoque); + const totais = groupTotalBySku(rowsFiltrados, { excludeRetrabalho: true }); el.totaisSkuBody.innerHTML = ''; totais.forEach((item) => { const tr = document.createElement('tr'); tr.innerHTML = `${item.sku}${item.total_paletes}`; el.totaisSkuBody.appendChild(tr); }); + + renderSobrasB01(); + renderConsultaChao(rowsFiltrados); + renderConsultaSkuArea(rowsFiltrados); + renderConsultaMapaHint(); + renderPlanejamentoTable(getPlanejamentoOcupacaoAtual(), 0); + renderOcupacao(); +} + +function inferContagemScopeFromPosicao(posicao) { + const p = normalizeAreaCode(posicao); + if (/^A\d+$/.test(p)) return 'A'; + if (/^B\d+[ED]?$/.test(p) || /^BD\d+$/.test(p) || /^BE\d+$/.test(p)) return 'B'; + if (/^C\d+$/.test(p)) return 'C'; + if (/^(R\d+\.\d+|CHAOESTRUTURA)$/.test(p)) return 'ESTRUTURA'; + if (p.startsWith('TISSUE')) return 'TISSUE'; + if (p.startsWith('LONIL')) return 'LONIL'; + if (p.startsWith('TTD')) return 'TTD'; + return null; +} + +function getOccupiedByWarehouse() { + const fromEstoque = { principal: 0, tissue: 0, lonil: 0, ttd: 0 }; + cache.estoque.forEach((row) => { + const area = normalizeAreaCode(row.area); + const pal = Number(row.paletes || 0); + if (/^TISSUE\d+[ED]?$/.test(area)) { + fromEstoque.tissue += pal; + } else if (/^LONIL\d+[ED]?$/.test(area)) { + fromEstoque.lonil += pal; + } else if (/^TTD\d+[ED]?$/.test(area)) { + fromEstoque.ttd += pal; + } else if (/^(A|B|C)\d+[ED]?$/.test(area)) { + fromEstoque.principal += pal; + } + }); + + const fromContagem = { principal: 0, tissue: 0, lonil: 0, ttd: 0, linhas: 0 }; + Object.entries(contagemMap || {}).forEach(([posicao, rawEntries]) => { + const scope = inferContagemScopeFromPosicao(posicao); + if (!scope) return; + const entries = Array.isArray(rawEntries) ? rawEntries : (rawEntries ? [rawEntries] : []); + entries.forEach((entry) => { + const result = computeContagem(posicao, entry, scope); + if (result.paletes <= 0) return; + if (['A', 'B', 'C'].includes(scope)) fromContagem.principal += Number(result.paletes || 0); + else if (scope === 'TISSUE') fromContagem.tissue += Number(result.paletes || 0); + else if (scope === 'LONIL') fromContagem.lonil += Number(result.paletes || 0); + else if (scope === 'TTD') fromContagem.ttd += Number(result.paletes || 0); + fromContagem.linhas += 1; + }); + }); + + const useContagem = fromContagem.linhas > 0; + return { + principal: useContagem ? fromContagem.principal : fromEstoque.principal, + tissue: useContagem ? fromContagem.tissue : fromEstoque.tissue, + lonil: useContagem ? fromContagem.lonil : fromEstoque.lonil, + ttd: useContagem ? fromContagem.ttd : fromEstoque.ttd, + source: useContagem ? 'contagem' : 'sistema' + }; +} + +function getPrincipalCapacidadePelasRegras() { + const calc = (bloco) => { + const posicoesUteis = Math.max(0, ocupacaoRules.posicoesPorRua - Number(ocupacaoRules.interditados[bloco] || 0)); + return posicoesUteis * ocupacaoRules.larguraPl2 * Number(ocupacaoRules.profundidade[bloco] || 0); + }; + return calc('A') + calc('BD') + calc('BE') + calc('C'); +} + +function getPaletesResumoPorSetor() { + const { principal, tissue, lonil, ttd } = getOccupiedByWarehouse(); + return { principal, tissue, lonil, ttd }; +} + +function exportOcupacaoResumoPDF() { + if (!window.jspdf) return showFeedback('Biblioteca de PDF não carregada.', 'error'); + const resumo = getPaletesResumoPorSetor(); + const linhas = [ + ['Principal (Ruas A/B/C + Estruturas)', resumo.principal], + ['Tissue', resumo.tissue], + ['Lonil', resumo.lonil], + ['TTD', resumo.ttd] + ]; + + const { jsPDF } = window.jspdf; + const pdf = new jsPDF('portrait'); + let y = 15; + pdf.setFontSize(14); + pdf.text('Resumo de Paletes por Setor', 10, y); + y += 8; + pdf.setFontSize(10); + pdf.text(`Gerado em: ${new Date().toLocaleString('pt-BR')}`, 10, y); + y += 10; + + linhas.forEach(([nome, qtd]) => { + pdf.text(`${nome}: ${qtd} paletes`, 10, y); + y += 7; + }); + + y += 2; + const total = linhas.reduce((acc, item) => acc + Number(item[1] || 0), 0); + pdf.setFontSize(11); + pdf.text(`Total geral: ${total} paletes`, 10, y); + pdf.save('resumo_paletes_setores.pdf'); + showFeedback('Resumo de paletes exportado em PDF com sucesso.'); +} + +function renderOcupacao() { + if (!el.ocupacaoBody) return; + el.ocupacaoBody.innerHTML = ''; + + const { principal, tissue, lonil, ttd, source } = getOccupiedByWarehouse(); + const data = new Date().toLocaleDateString('pt-BR', { day: '2-digit', month: 'short' }); + const cap = { g1: 4402, g2: 2520, g3: 1771, g4: 846 }; + const terceiros = { g1: 856, g2: 0, g3: 521, g4: 846 }; + const dispTotal = { + g1: cap.g1 - terceiros.g1, + g2: cap.g2 - terceiros.g2, + g3: cap.g3 - terceiros.g3, + g4: cap.g4 - terceiros.g4 + }; + const ocupadoJsl = { g1: principal, g2: tissue, g3: lonil, g4: ttd }; + const dispJsl = { + g1: dispTotal.g1 - ocupadoJsl.g1, + g2: dispTotal.g2 - ocupadoJsl.g2, + g3: dispTotal.g3 - ocupadoJsl.g3, + g4: dispTotal.g4 - ocupadoJsl.g4 + }; + const capacidadeFardos = 350000; + const qtdFardos = cache.estoque.reduce((acc, row) => { + const fpp = getFardosPorPalete(row.sku); + return acc + (fpp ? (Number(row.paletes || 0) * fpp) : 0); + }, 0); + + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${data}${cap.g1}${terceiros.g1}${dispTotal.g1}${ocupadoJsl.g1}${dispJsl.g1} + ${data}${cap.g2}${terceiros.g2}${dispTotal.g2}${ocupadoJsl.g2}${dispJsl.g2} + ${data}${cap.g3}${terceiros.g3}${dispTotal.g3}${ocupadoJsl.g3}${dispJsl.g3} + ${data}${cap.g4}${terceiros.g4}${dispTotal.g4}${ocupadoJsl.g4}${dispJsl.g4} + ${capacidadeFardos}${qtdFardos.toLocaleString('pt-BR')} + `; + el.ocupacaoBody.appendChild(tr); + + setStatus(el.ocupacaoStatus, source === 'contagem' + ? 'Ocupação atualizada pela Contagem (layout em linha reta G1/G2/G3/G4).' + : 'Ocupação atualizada com dados do sistema (layout em linha reta G1/G2/G3/G4).', 'success'); +} + +function calcularLinhaG1(item) { + const total = Math.max(0, (Number(item.posicoes) * Number(item.palletPosicao)) - Number(item.bloqueado)); + const disponivel = Math.max(0, total - Number(item.terceiros)); + return { total, disponivel }; +} + +function renderG1Detalhe() { + if (!el.g1DetalheBody) return; + el.g1DetalheBody.innerHTML = ''; + + let totalGeral = 0; + let terceirosGeral = 0; + let disponivelGeral = 0; + + g1Modelo.forEach((item) => { + const { total, disponivel } = calcularLinhaG1(item); + totalGeral += total; + terceirosGeral += Number(item.terceiros); + disponivelGeral += disponivel; + + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${item.bloco} + ${item.posicoes} + ${item.palletPosicao} + + ${total} + + ${disponivel} + `; + el.g1DetalheBody.appendChild(tr); + }); + + setStatus(el.g1TotaisStatus, `G1 total: ${totalGeral} | terceiros: ${terceirosGeral} | disponível: ${disponivelGeral}.`, 'success'); + + el.g1DetalheBody.querySelectorAll('input').forEach((input) => { + input.addEventListener('change', (event) => { + const bloco = event.target.dataset.bloco; + const field = event.target.dataset.field; + const row = g1Modelo.find((i) => i.bloco === bloco); + if (!row) return; + row[field] = Math.max(0, Number(event.target.value || 0)); + renderG1Detalhe(); + renderOcupacao(); + }); + }); } function renderMovimentacoes() { + if (!el.movimentacoesBody) return; el.movimentacoesBody.innerHTML = ''; cache.movimentacoes.forEach((row) => { const tr = document.createElement('tr'); @@ -191,21 +778,718 @@ function renderMovimentacoes() { }); } +const estruturaLabels = [ + 'R1.01-07', 'R2.01-09', 'R3.01-09', 'R3.10-16', 'R4.01-09', 'R4.10-16', + 'R5.01-09', 'R5.10-16', 'R6.01-10', 'R6.11-16', 'CHAO ESTRUTURA' +]; + +function expandRangeLabel(label) { + const normalized = normalizeText(label); + if (normalized === 'CHAO ESTRUTURA') return ['CHAO ESTRUTURA']; + const match = normalized.match(/^(R\d+)\.(\d+)-(\d+)$/); + if (!match) return [normalized]; + const prefix = match[1]; + const start = Number(match[2]); + const end = Number(match[3]); + const values = []; + for (let i = start; i <= end; i += 1) values.push(`${prefix}.${String(i).padStart(2, '0')}`); + return values; +} + +function shouldShowContagemSide() { + const scope = normalizeText(el.contagemScope?.value); + return ['B', 'TISSUE', 'TTD', 'LONIL'].includes(scope); +} + +function updateContagemSideVisibility() { + if (!el.contagemSide) return; + const visible = shouldShowContagemSide(); + const sideLabel = document.getElementById('contagemSideLabel'); + sideLabel?.classList.toggle('hidden', !visible); + if (!visible) el.contagemSide.value = 'ALL'; +} + +function getContagemPositions(scope, side = 'ALL') { + const s = normalizeText(scope); + if (s === 'A') return Array.from({ length: 26 }, (_, i) => `A${String(i + 1).padStart(2, '0')}`); + if (s === 'C') return Array.from({ length: 26 }, (_, i) => `C${String(i + 1).padStart(2, '0')}`); + if (s === 'B') { + const b = []; + for (let i = 1; i <= 22; i += 1) { + const base = `B${String(i).padStart(2, '0')}`; + if (side === 'D') b.push(`${base}D`); + else if (side === 'E') b.push(`${base}E`); + else { + b.push(`${base}D`); + b.push(`${base}E`); + } + } + return b; + } + if (s === 'ESTRUTURA') return [...new Set(estruturaLabels.flatMap(expandRangeLabel))]; + if (['TISSUE', 'TTD', 'LONIL'].includes(s)) { + const fromDb = cache.estoque + .map((row) => normalizeAreaCode(row.area)) + .filter((area) => area.startsWith(s)); + const maxPos = fromDb.reduce((max, area) => { + const m = area.match(/^(?:TISSUE|TTD|LONIL)(\d+)/); + return m ? Math.max(max, Number(m[1])) : max; + }, 0); + const qty = maxPos || 20; + const out = []; + for (let i = 1; i <= qty; i += 1) { + const base = `${s}${String(i).padStart(2, '0')}`; + if (side === 'D') out.push(`${base}D`); + else if (side === 'E') out.push(`${base}E`); + else { + out.push(`${base}D`); + out.push(`${base}E`); + } + } + return out; + } + return []; +} + +function getContagemEntries(posicao) { + const raw = contagemMap[posicao]; + const asArray = Array.isArray(raw) ? raw : (raw ? [raw] : []); + if (!asArray.length) return [createEmptyContagemEntry()]; + return asArray.map((item) => ({ + sku: item.sku || '', + profundidade1: Number(item.profundidade1 || 0), + largura1: Number(item.largura1 || 0), + segundaCamada: typeof item.segundaCamada === 'boolean' ? item.segundaCamada : (Number(item.profundidade2 || 0) > 0 || Number(item.largura2 || 0) > 0), + profundidade2: Number(item.profundidade2 || 0), + largura2: Number(item.largura2 || 0), + terceiraCamada: Boolean(item.terceiraCamada), + paletesTerceira: Number(item.paletesTerceira || 0), + fardosFaltando: Number(item.fardosFaltando || 0), + totalManual: Number(item.totalManual || 0), + usarTotalManual: Boolean(item.usarTotalManual), + blocadoPresente: typeof item.blocadoPresente === 'boolean' ? item.blocadoPresente : true, + confirmada: Boolean(item.confirmada), + tipoPlt: normalizeText(item.tipoPlt) + })); +} + +function createEmptyContagemEntry() { + return { + sku: '', + profundidade1: 0, + largura1: 0, + segundaCamada: false, + profundidade2: 0, + largura2: 0, + terceiraCamada: false, + paletesTerceira: 0, + fardosFaltando: 0, + totalManual: 0, + usarTotalManual: false, + blocadoPresente: true, + confirmada: false, + tipoPlt: '' + }; +} + +function saveContagemEntries(posicao, entries) { + const normalized = entries.map((entry) => ({ + ...createEmptyContagemEntry(), + ...entry + })); + contagemMap[posicao] = normalized; + storageSet('wmss_contagem_map', JSON.stringify(contagemMap)); +} + +function addContagemEntry(posicao) { + const entries = getContagemEntries(posicao); + entries.push(createEmptyContagemEntry()); + saveContagemEntries(posicao, entries); +} + +function removeContagemEntry(posicao, idx) { + const entries = getContagemEntries(posicao); + if (entries.length <= 1) return; + entries.splice(idx, 1); + saveContagemEntries(posicao, entries); +} + +function computeContagem(posicao, entry, scope = el.contagemScope?.value) { + const st = { ...createEmptyContagemEntry(), ...(entry || {}) }; + const isEstrutura = normalizeText(scope) === 'ESTRUTURA'; + const ativo = st.blocadoPresente !== false; + if (!ativo) { + const paletes = st.usarTotalManual && Number(st.totalManual) > 0 ? Number(st.totalManual) : 0; + const fpp = getFardosPorPalete(st.sku); + const faltando = Math.max(0, st.fardosFaltando || 0); + const fardosBrutos = fpp ? paletes * fpp : null; + const fardos = Number.isFinite(fardosBrutos) ? Math.max(0, fardosBrutos - faltando) : null; + return { ...st, posicao, paletes, paletesCalculados: 0, fardos }; + } + const basePrimeira = Math.max(0, st.profundidade1 * st.largura1); + const baseSegunda = st.segundaCamada ? Math.max(0, st.profundidade2 * st.largura2) : 0; + const base = isEstrutura ? (normalizeText(st.sku) ? 1 : 0) : basePrimeira + baseSegunda; + const terceira = st.terceiraCamada ? Math.max(0, st.paletesTerceira) : 0; + const paletesCalculados = base + terceira; + const paletes = st.usarTotalManual && Number(st.totalManual) > 0 ? Number(st.totalManual) : paletesCalculados; + const fpp = getFardosPorPalete(st.sku); + const faltando = Math.max(0, st.fardosFaltando || 0); + const fardosBrutos = fpp ? paletes * fpp : null; + const fardos = Number.isFinite(fardosBrutos) ? Math.max(0, fardosBrutos - faltando) : null; + return { ...st, posicao, paletes, paletesCalculados, fardos }; +} + +function dividirQuantidade(total, slots) { + const qtd = Math.max(0, Number(total) || 0); + if (!slots) return []; + const base = Math.floor(qtd / slots); + const resto = qtd % slots; + return Array.from({ length: slots }, (_, idx) => base + (idx < resto ? 1 : 0)); +} + +function estimateLayersFromTotal(totalPaletes) { + const total = Math.max(0, Number(totalPaletes) || 0); + const capacidadeCamada = 15; + const primeira = Math.min(total, capacidadeCamada); + const segunda = Math.min(Math.max(0, total - capacidadeCamada), capacidadeCamada); + const terceira = Math.max(0, total - (capacidadeCamada * 2)); + + const toDepthWidth = (qty) => { + if (qty <= 0) return { profundidade: 0, largura: 0 }; + const profundidade = Math.min(5, qty); + const largura = Math.min(3, Math.ceil(qty / profundidade)); + return { profundidade, largura }; + }; + + const c1 = toDepthWidth(primeira); + const c2 = toDepthWidth(segunda); + return { + profundidade1: c1.profundidade, + largura1: c1.largura, + segundaCamada: segunda > 0, + profundidade2: c2.profundidade, + largura2: c2.largura, + terceiraCamada: terceira > 0, + paletesTerceira: terceira + }; +} + +function hasManualContagemData(entry) { + return Boolean( + normalizeText(entry?.sku) + || Number(entry?.totalManual) > 0 + || Number(entry?.profundidade1) > 0 + || Number(entry?.largura1) > 0 + || Number(entry?.profundidade2) > 0 + || Number(entry?.largura2) > 0 + || Number(entry?.paletesTerceira) > 0 + || Boolean(entry?.usarTotalManual) + || Number(entry?.fardosFaltando) > 0 + ); +} + +function estimateContagemFromTurno(scope, side = 'ALL') { + const skuTotals = turnoUltimaPlanilhaSku || {}; + const skusDisponiveis = Object.keys(skuTotals).filter((sku) => Number(skuTotals[sku]) > 0); + if (!skusDisponiveis.length) return 0; + + const positions = getContagemPositions(scope, side); + const targetsBySku = {}; + positions.forEach((posicao) => { + const entries = getContagemEntries(posicao); + entries.forEach((entry, idx) => { + const sku = normalizeText(entry.sku); + if (!sku || !Number(skuTotals[sku])) return; + if (hasManualContagemData(entry)) return; + if (!targetsBySku[sku]) targetsBySku[sku] = []; + targetsBySku[sku].push({ posicao, idx }); + }); + }); + + let estimadas = 0; + Object.entries(targetsBySku).forEach(([sku, targets]) => { + const distribuicao = dividirQuantidade(Number(skuTotals[sku] || 0), targets.length); + targets.forEach((target, i) => { + const entries = getContagemEntries(target.posicao); + const current = { ...entries[target.idx] }; + const total = distribuicao[i] || 0; + current.totalManual = total; + current.usarTotalManual = true; + if (normalizeText(scope) !== 'ESTRUTURA') Object.assign(current, estimateLayersFromTotal(total)); + entries[target.idx] = current; + saveContagemEntries(target.posicao, entries); + estimadas += 1; + }); + }); + + return estimadas; +} + +async function importContagemFromPlanilha() { + const file = el.contagemFile?.files?.[0]; + if (!file) return setStatus(el.contagemStatus, 'Selecione a planilha para pré-preencher a contagem.', 'error'); + + try { + const buffer = await file.arrayBuffer(); + const workbook = XLSX.read(buffer, { type: 'array' }); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const rawRows = XLSX.utils.sheet_to_json(sheet, { defval: '' }); + const scope = el.contagemScope?.value; + const positionsAll = new Set(['A', 'B', 'C', 'ESTRUTURA', 'TISSUE', 'LONIL', 'TTD'] + .flatMap((s) => getContagemPositions(s, 'ALL'))); + const normalizeHeader = (key) => String(key || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim(); + const parseDepositoScope = (deposito) => { + const dep = normalizeText(deposito); + if (!dep) return ''; + if (dep.includes('ESTRUT')) return 'ESTRUTURA'; + if (dep.includes('TISSUE')) return 'TISSUE'; + if (dep.includes('LONIL')) return 'LONIL'; + if (dep.includes('TTD')) return 'TTD'; + if (dep.includes('PRINCIPAL')) return 'PRINCIPAL'; + return ''; + }; + + const planRows = rawRows + .map((raw) => { + const row = Object.fromEntries(Object.entries(raw).map(([k, v]) => [normalizeHeader(k), v])); + return { + deposito: normalizeText(row.deposito ?? row.setor ?? row.rua ?? row.area_contagem), + quadrante: normalizeText(row.quadrante ?? row.area ?? row.posicao ?? row.endereco), + area: normalizeAreaCode(row.quadrante ?? row.area ?? row.posicao ?? row.endereco), + sku: Number(row.sku ?? row.codsku ?? row.cod_sku), + paletes: Number(row.qtd_plt ?? row['qtd plt'] ?? row.paletes ?? row.pallets ?? row.quantidade), + tipoPlt: normalizeText(row.tipo_plt ?? row['tipo plt'] ?? row.tipo), + lado: normalizeText(row.lado ?? row.side), + setor: normalizeText(row.setor ?? row.rua ?? row.area_contagem) + }; + }) + .filter((row) => Number.isFinite(row.sku) && row.paletes > 0) + .map((row) => ({ ...row, depositoScope: parseDepositoScope(row.deposito) })); + + const autoIndex = {}; + const validRows = planRows + .map((row) => { + let area = normalizeAreaCode(row.area); + let guessScope = row.depositoScope; + if (!guessScope && /^A\d+/.test(area)) guessScope = 'A'; + if (!guessScope && /^B\d+[DE]?$/.test(area)) guessScope = 'B'; + if (!guessScope && /^C\d+/.test(area)) guessScope = 'C'; + if (!guessScope && area.startsWith('TISSUE')) guessScope = 'TISSUE'; + if (!guessScope && area.startsWith('LONIL')) guessScope = 'LONIL'; + if (!guessScope && area.startsWith('TTD')) guessScope = 'TTD'; + if (!guessScope && /^(R\d+\.\d+|CHAOESTRUTURA)$/.test(area)) guessScope = 'ESTRUTURA'; + + if (!area && ['TISSUE', 'TTD', 'LONIL'].includes(guessScope)) { + const sideFromQuadrante = row.quadrante.includes('DIREIT') ? 'D' : (row.quadrante.includes('ESQUERD') ? 'E' : ''); + const preferredSide = row.lado === 'DIREITO' ? 'D' + : row.lado === 'ESQUERDO' ? 'E' + : (['D', 'E'].includes(row.lado) ? row.lado : (sideFromQuadrante || 'D')); + const key = `${guessScope}_${preferredSide}`; + autoIndex[key] = (autoIndex[key] || 0) + 1; + area = `${guessScope}${String(autoIndex[key]).padStart(2, '0')}${preferredSide}`; + } + + if (!area && ['A', 'B', 'C', 'PRINCIPAL'].includes(guessScope)) return null; + if ((guessScope === 'A' || /^A/.test(area)) && /^\d+$/.test(area)) area = `A${String(Number(area)).padStart(2, '0')}`; + if ((guessScope === 'C' || /^C/.test(area)) && /^\d+$/.test(area)) area = `C${String(Number(area)).padStart(2, '0')}`; + if ((guessScope === 'B' || guessScope === 'PRINCIPAL') && /^B?\d+$/.test(area)) { + const num = String(Number(area.replace(/^B/, ''))).padStart(2, '0'); + const bSide = row.quadrante.includes('ESQUERD') || row.lado === 'E' || row.lado === 'ESQUERDO' ? 'E' : 'D'; + area = `B${num}${bSide}`; + } + + if ((guessScope === 'PRINCIPAL' || guessScope === 'B') && /^B\d+[DE]$/.test(area)) guessScope = 'B'; + if ((guessScope === 'PRINCIPAL' || !guessScope) && /^A\d+$/.test(area)) guessScope = 'A'; + if ((guessScope === 'PRINCIPAL' || !guessScope) && /^C\d+$/.test(area)) guessScope = 'C'; + + return { ...row, area, tipoPlt: ['PL2', 'PBR'].includes(row.tipoPlt) ? row.tipoPlt : '' }; + }) + .filter((row) => row && positionsAll.has(normalizeAreaCode(row.area))) + .sort((a, b) => normalizeAreaCode(a.area).localeCompare(normalizeAreaCode(b.area), 'pt-BR', { numeric: true })); + + if (!validRows.length) { + setStatus(el.contagemStatus, 'Nenhuma linha válida encontrada na planilha. Verifique colunas sku/deposito/quadrante/qtd plt/tipo plt.', 'error'); + return; + } + + if (el.contagemImportResetToggle?.checked) { + contagemMap = {}; + storageSet('wmss_contagem_map', JSON.stringify(contagemMap)); + } + + const grouped = validRows.reduce((acc, row) => { + const area = normalizeAreaCode(row.area); + if (!acc[area]) acc[area] = []; + acc[area].push(row); + return acc; + }, {}); + + Object.entries(grouped).forEach(([area, group]) => { + const entries = group.map((row) => { + const total = Number(row.paletes || 0); + return { + ...createEmptyContagemEntry(), + sku: String(row.sku), + totalManual: total, + confirmada: false, + tipoPlt: row.tipoPlt, + ...(normalizeText(scope) === 'ESTRUTURA' ? {} : estimateLayersFromTotal(total)) + }; + }); + saveContagemEntries(area, entries); + }); + + renderContagemTable(); + setStatus(el.contagemStatus, `Planilha carregada. ${validRows.length} linha(s) aplicadas para conferência manual. Marque "Confirmar" nas linhas corretas e clique em "Atualizar consulta".`, 'success'); + } catch (error) { + setStatus(el.contagemStatus, `Erro ao ler planilha da contagem: ${error.message}`, 'error'); + } +} + +function inferTipoContagem(posicao, sku) { + const samePos = cache.estoque.find((row) => normalizeAreaCode(row.area) === normalizeAreaCode(posicao) && Number(row.sku) === Number(sku)); + if (samePos?.tipo) return normalizeText(samePos.tipo); + const sameSku = cache.estoque.find((row) => Number(row.sku) === Number(sku)); + if (sameSku?.tipo) return normalizeText(sameSku.tipo); + return 'PL2'; +} + +async function applyContagemToConsulta() { + if (!supabaseClient) return setStatus(el.contagemStatus, 'Banco não conectado para atualizar consulta.', 'error'); + const scope = el.contagemScope?.value; + const side = el.contagemSide?.value || 'ALL'; + const positions = getContagemPositions(scope, side); + const confirmedRows = positions.flatMap((posicao) => getContagemEntries(posicao) + .map((entry) => computeContagem(posicao, entry, scope)) + .filter((row) => row.confirmada && normalizeText(row.sku) && row.paletes > 0) + .map((row) => ({ + area: normalizeAreaCode(row.posicao), + sku: Number(row.sku), + tipo: ['PL2', 'PBR'].includes(normalizeText(row.tipoPlt)) ? normalizeText(row.tipoPlt) : inferTipoContagem(row.posicao, row.sku), + paletes: Number(row.paletes) + }))); + + if (!confirmedRows.length) { + return setStatus(el.contagemStatus, 'Nenhuma linha confirmada para atualizar a consulta.', 'error'); + } + + const targetAreas = [...new Set(confirmedRows.map((row) => row.area))]; + try { + const { error: deleteError } = await supabaseClient + .from('estoque_area') + .delete() + .in('area', targetAreas); + if (deleteError) throw deleteError; + + const { error: insertError } = await supabaseClient + .from('estoque_area') + .upsert(confirmedRows); + if (insertError) throw insertError; + + await loadAll(); + setStatus(el.contagemStatus, `Consulta atualizada com ${confirmedRows.length} linha(s) confirmada(s) em ${targetAreas.length} posição(ões).`, 'success'); + showFeedback('Contagem confirmada aplicada na consulta com sucesso.'); + } catch (error) { + setStatus(el.contagemStatus, `Erro ao atualizar consulta pela contagem: ${error.message}`, 'error'); + } +} + + +function renderContagemResumo(rows) { + if (!el.contagemResumoBody) return; + el.contagemResumoBody.innerHTML = ''; + const grouped = rows.reduce((acc, row) => { + const sku = normalizeText(row.sku); + if (!sku || row.paletes <= 0) return acc; + if (!acc[sku]) acc[sku] = { sku, paletes: 0, fardos: 0, missing: false }; + acc[sku].paletes += row.paletes; + if (Number.isFinite(row.fardos)) acc[sku].fardos += row.fardos; + else acc[sku].missing = true; + return acc; + }, {}); + const rowsResumo = Object.values(grouped).sort((a, b) => Number(a.sku) - Number(b.sku)); + rowsResumo.forEach((row) => { + const tr = document.createElement('tr'); + tr.innerHTML = `${row.sku}${row.paletes}${row.missing ? 'SKU sem fardos/palete' : row.fardos}`; + el.contagemResumoBody.appendChild(tr); + }); + if (!rowsResumo.length) { + const tr = document.createElement('tr'); + tr.innerHTML = 'Sem posições preenchidas.'; + el.contagemResumoBody.appendChild(tr); + } +} + +function preloadContagemFromEstoque(scope, side = 'ALL') { + const positions = new Set(getContagemPositions(scope, side)); + let preenchidas = 0; + + positions.forEach((posicao) => { + const existentes = getContagemEntries(posicao); + const temDadosDigitados = existentes.some((entry) => normalizeText(entry.sku) || Number(entry.profundidade1) > 0 || Number(entry.largura1) > 0 || Number(entry.profundidade2) > 0 || Number(entry.largura2) > 0 || Number(entry.paletesTerceira) > 0 || Boolean(entry.usarTotalManual) || Number(entry.fardosFaltando) > 0 || Number(entry.totalManual) > 0); + if (temDadosDigitados) return; + + const rows = cache.estoque + .filter((row) => normalizeAreaCode(row.area) === posicao) + .sort((a, b) => Number(a.sku) - Number(b.sku)); + + if (!rows.length) return; + + const entries = rows.map((row) => ({ + ...createEmptyContagemEntry(), + sku: String(row.sku || '') + })); + + saveContagemEntries(posicao, entries); + preenchidas += 1; + }); + + return preenchidas; +} + +function renderContagemTable() { + if (!el.contagemBody) return; + updateContagemSideVisibility(); + const scope = el.contagemScope?.value; + const isEstrutura = normalizeText(scope) === 'ESTRUTURA'; + const positions = getContagemPositions(scope, el.contagemSide?.value || 'ALL'); + el.contagemBody.innerHTML = ''; + const computedRows = []; + + positions.forEach((posicao) => { + const entries = getContagemEntries(posicao); + + entries.forEach((entry, idx) => { + const result = computeContagem(posicao, entry, scope); + computedRows.push(result); + const tr = document.createElement('tr'); + const sideHint = sideLabel(getSideFromArea(posicao)); + const posLabel = idx === 0 ? `${posicao} ${sideHint}` : `↳ ${posicao} ${sideHint}`; + const plusOrRemove = idx === 0 + ? `` + : ``; + + tr.innerHTML = ` + ${plusOrRemove} + ${posLabel} + + + + ${isEstrutura || !entry.blocadoPresente ? '-' : ``} + ${isEstrutura || !entry.blocadoPresente ? '-' : ``} + ${isEstrutura || !entry.blocadoPresente ? '-' : ``} + ${isEstrutura || !entry.blocadoPresente ? '-' : (entry.segundaCamada ? `` : 'marque 2ª camada')} + ${isEstrutura || !entry.blocadoPresente ? '-' : (entry.segundaCamada ? `` : 'marque 2ª camada')} + ${!entry.blocadoPresente ? '-' : ``} + ${!entry.blocadoPresente ? '-' : ``} + + + + ${result.paletes} + ${Number.isFinite(result.fardos) ? result.fardos : '-'} + `; + el.contagemBody.appendChild(tr); + }); + }); + + el.contagemBody.querySelectorAll('button[data-action="add-entry"]').forEach((btn) => { + btn.addEventListener('click', (event) => { + addContagemEntry(event.currentTarget.dataset.posicao); + renderContagemTable(); + }); + }); + + el.contagemBody.querySelectorAll('button[data-action="remove-entry"]').forEach((btn) => { + btn.addEventListener('click', (event) => { + const { posicao, entryIdx } = event.currentTarget.dataset; + removeContagemEntry(posicao, Number(entryIdx)); + renderContagemTable(); + }); + }); + + el.contagemBody.querySelectorAll('input').forEach((input) => { + input.addEventListener('keydown', (event) => { + if (event.key !== 'Enter') return; + event.preventDefault(); + focusNextContagemInput(event.currentTarget); + }); + + input.addEventListener('change', (event) => { + const { posicao, entryIdx, field } = event.target.dataset; + const entries = getContagemEntries(posicao); + const idx = Number(entryIdx || 0); + const current = { ...entries[idx] }; + current[field] = event.target.type === 'checkbox' ? event.target.checked : event.target.value; + if (field === 'segundaCamada' && !event.target.checked) { + current.profundidade2 = 0; + current.largura2 = 0; + } + if (field === 'blocadoPresente' && !event.target.checked) { + current.profundidade1 = 0; + current.largura1 = 0; + current.segundaCamada = false; + current.profundidade2 = 0; + current.largura2 = 0; + current.terceiraCamada = false; + current.paletesTerceira = 0; + } + if (field === 'terceiraCamada' && !event.target.checked) current.paletesTerceira = 0; + if (field === 'usarTotalManual' && !event.target.checked) current.totalManual = 0; + entries[idx] = current; + saveContagemEntries(posicao, entries); + renderContagemTable(); + }); + }); + + renderContagemResumo(computedRows); +} + + +function focusNextContagemInput(currentInput) { + if (!el.contagemBody || !currentInput) return; + const fields = Array.from(el.contagemBody.querySelectorAll('input')) + .filter((node) => !node.disabled && node.type !== 'hidden'); + const idx = fields.indexOf(currentInput); + if (idx < 0) return; + const next = fields[idx + 1]; + if (!next) return; + next.focus(); + if (next.type !== 'checkbox') next.select?.(); +} + +function exportContagemExcel() { + const allScopes = ['A', 'B', 'C', 'ESTRUTURA', 'TISSUE', 'TTD', 'LONIL']; + const rows = allScopes + .flatMap((scope) => getContagemPositions(scope, 'ALL') + .flatMap((p) => getContagemEntries(p).map((entry, idx) => ({ ...computeContagem(p, entry, scope), entry_idx: idx + 1, scope })))) + .filter((row) => normalizeText(row.sku) && row.paletes > 0) + .map((row) => ({ + area_contagem: row.scope, + posicao: row.posicao, + sku: row.sku, + confirmada: row.confirmada ? 'SIM' : 'NAO', + item_posicao: row.entry_idx, + profundidade_1: row.profundidade1, + largura_1: row.largura1, + segunda_camada: row.segundaCamada ? 'SIM' : 'NAO', + profundidade_2: row.profundidade2, + largura_2: row.largura2, + terceira_camada: row.terceiraCamada ? 'SIM' : 'NAO', + paletes_terceira: row.terceiraCamada ? row.paletesTerceira : 0, + fardos_faltando: row.fardosFaltando || 0, + usar_total_editavel: row.usarTotalManual ? 'SIM' : 'NAO', + total_editavel: row.totalManual || 0, + paletes_totais: row.paletes, + fardos_totais: Number.isFinite(row.fardos) ? row.fardos : '' + })); + if (!rows.length) return showFeedback('Nenhuma posição preenchida para exportar.', 'error'); + const resumo = Object.values(rows.reduce((acc, row) => { + const sku = String(row.sku); + if (!acc[sku]) acc[sku] = { sku, paletes: 0, fardos: 0 }; + acc[sku].paletes += Number(row.paletes_totais || 0); + acc[sku].fardos += Number(row.fardos_totais || 0); + return acc; + }, {})); + exportWorkbook('contagem.xlsx', [ + { name: 'Contagem', data: rows }, + { name: 'Resumo_SKU', data: resumo } + ]); + showFeedback('Contagem exportada com sucesso (todas as áreas preenchidas).'); +} + +function setupContagem() { + updateContagemSideVisibility(); + el.contagemScope?.addEventListener('change', () => { + updateContagemSideVisibility(); + renderContagemTable(); + }); + el.contagemSide?.addEventListener('change', renderContagemTable); + el.contagemLoadBtn?.addEventListener('click', () => { + const scope = el.contagemScope?.value; + const side = el.contagemSide?.value || 'ALL'; + const preenchidas = preloadContagemFromEstoque(scope, side) || 0; + const estimadas = estimateContagemFromTurno(scope, side); + renderContagemTable(); + setStatus(el.contagemStatus, `Posições carregadas para preenchimento. SKU(s) sugeridos em ${preenchidas} posição(ões). Estimativa da conferência aplicada em ${estimadas} linha(s).`, 'success'); + }); + el.contagemImportBtn?.addEventListener('click', importContagemFromPlanilha); + el.contagemClearChecksBtn?.addEventListener('click', () => { + Object.keys(contagemMap || {}).forEach((posicao) => { + const entries = getContagemEntries(posicao).map((entry) => ({ + ...entry, + confirmada: false, + blocadoPresente: true, + usarTotalManual: false, + segundaCamada: false, + terceiraCamada: false + })); + saveContagemEntries(posicao, entries); + }); + renderContagemTable(); + setStatus(el.contagemStatus, 'Checkboxes limpos para iniciar novo turno.', 'success'); + }); + el.contagemEstimateBtn?.addEventListener('click', () => { + const scope = el.contagemScope?.value; + const side = el.contagemSide?.value || 'ALL'; + const estimadas = estimateContagemFromTurno(scope, side); + renderContagemTable(); + setStatus(el.contagemStatus, estimadas + ? `Estimativa da última conferência aplicada em ${estimadas} linha(s). Campos continuam editáveis, inclusive o total.` + : 'Não há dados da última conferência para estimar (suba a planilha na aba Conferência de turno).', estimadas ? 'success' : 'error'); + }); + el.contagemApplyBtn?.addEventListener('click', applyContagemToConsulta); + el.contagemForm?.addEventListener('submit', (event) => { + event.preventDefault(); + renderContagemTable(); + setStatus(el.contagemStatus, 'Cálculo atualizado.', 'success'); + }); + el.contagemExportBtn?.addEventListener('click', exportContagemExcel); +} + async function handleEstoqueSubmit(event) { event.preventDefault(); if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error'); const formData = new FormData(event.target); const payload = { - area: normalizeText(formData.get('area')), + area: normalizeAreaCode(formData.get('area')), sku: Number(formData.get('sku')), tipo: normalizeText(formData.get('tipo')), paletes: Number(formData.get('paletes')) }; + const fardosInput = Number(formData.get('fardos_por_palete')); + const fardosExistente = getFardosPorPalete(payload.sku); + if (!fardosExistente && (!Number.isFinite(fardosInput) || fardosInput <= 0)) { + return showFeedback('SKU sem fardos por palete cadastrado. Informe no campo "Fardos por palete (SKU novo)".', 'error'); + } + + const paletesIncompletos = Number(formData.get('paletes_incompletos')); + const fardosIncompletos = Number(formData.get('fardos_incompletos')); + const incompletoAtivo = el.paleteIncompletoToggle?.checked; + try { + if (!fardosExistente && Number.isFinite(fardosInput) && fardosInput > 0) { + const { error: produtoError } = await supabaseClient + .from('produtos') + .upsert({ sku: payload.sku, fardos_por_palete: fardosInput }); + if (produtoError) throw produtoError; + } + const { error } = await supabaseClient.from('estoque_area').upsert(payload); if (error) throw error; + + const chave = estoqueKey(payload.area, payload.sku, payload.tipo); + if (incompletoAtivo) { + if (!Number.isFinite(paletesIncompletos) || paletesIncompletos <= 0 || !Number.isFinite(fardosIncompletos) || fardosIncompletos <= 0) { + return showFeedback('Informe quantidade de paletes incompletos e total de fardos.', 'error'); + } + fracionadoMap[chave] = { paletes_incompletos: paletesIncompletos, fardos_incompletos: fardosIncompletos }; + } else { + delete fracionadoMap[chave]; + } + storageSet('wmss_fracionado_map', JSON.stringify(fracionadoMap)); + showFeedback('Estoque salvo com sucesso.'); event.target.reset(); await loadEstoque(); @@ -251,7 +1535,7 @@ async function handleExpedicaoSubmit(event) { if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error'); const formData = new FormData(event.target); - const area = normalizeText(formData.get('area')); + const area = normalizeAreaCode(formData.get('area')); const sku = Number(formData.get('sku')); const tipo = normalizeText(formData.get('tipo')); const paletes = Number(formData.get('paletes')); @@ -294,28 +1578,245 @@ async function handleExpedicaoSubmit(event) { } } +function consolidarPorChave(rows) { + return rows.reduce((acc, row) => { + const key = estoqueKey(row.area, row.sku, row.tipo); + acc[key] = (acc[key] || 0) + Number(row.paletes || 0); + return acc; + }, {}); +} + +function saveTurnoSnapshot(snapshotRows) { + const item = { + created_at: new Date().toISOString(), + rows: snapshotRows + }; + + turnoSnapshots = [item, ...turnoSnapshots].slice(0, 3); + storageSet('wmss_turno_snapshots', JSON.stringify(turnoSnapshots)); + renderTurnoHistory(); +} + +function renderTurnoHistory() { + if (!el.turnoHistoryBody) return; + el.turnoHistoryBody.innerHTML = ''; + + if (!turnoSnapshots.length) { + const tr = document.createElement('tr'); + tr.innerHTML = 'Sem snapshots salvos.'; + el.turnoHistoryBody.appendChild(tr); + return; + } + + turnoSnapshots.forEach((snap) => { + const tr = document.createElement('tr'); + tr.innerHTML = `${new Date(snap.created_at).toLocaleString('pt-BR')}${snap.rows.length}`; + el.turnoHistoryBody.appendChild(tr); + }); +} + +async function handleTurnoSubmit(event) { + event.preventDefault(); + const file = el.turnoFile?.files?.[0]; + if (!file) return setStatus(el.turnoStatus, 'Selecione a planilha de contagem final.', 'error'); + + try { + const buffer = await file.arrayBuffer(); + const workbook = XLSX.read(buffer, { type: 'array' }); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(sheet, { defval: '' }).map(mapImportRow); + turnoUltimaPlanilhaSku = rows.reduce((acc, row) => { + const sku = Number(row.sku); + const paletes = Number(row.paletes); + if (!Number.isFinite(sku) || !Number.isFinite(paletes) || paletes <= 0) return acc; + const key = String(sku); + acc[key] = (acc[key] || 0) + paletes; + return acc; + }, {}); + storageSet('wmss_turno_ultima_planilha_sku', JSON.stringify(turnoUltimaPlanilhaSku)); + + const snapshotAnterior = turnoSnapshots[0]?.rows ?? null; + const atual = snapshotAnterior ? consolidarPorChave(snapshotAnterior) : consolidarPorChave(cache.estoque); + const finalTurno = consolidarPorChave(rows); + const saidaSkuTipo = {}; + const missing = new Set(); + lastTurnoResultado = []; + + Object.keys(atual).forEach((key) => { + const saiu = Math.max(0, Number(atual[key] || 0) - Number(finalTurno[key] || 0)); + if (!saiu) return; + const [, sku, tipo] = key.split('|'); + const skuTipo = `${sku}|${tipo}`; + saidaSkuTipo[skuTipo] = (saidaSkuTipo[skuTipo] || 0) + saiu; + }); + + el.turnoSkuBody.innerHTML = ''; + Object.entries(saidaSkuTipo) + .sort(([a], [b]) => a.localeCompare(b, 'pt-BR', { numeric: true })) + .forEach(([skuTipo, paletes]) => { + const [sku, tipo] = skuTipo.split('|'); + const fpp = getFardosPorPalete(Number(sku)); + if (!fpp) missing.add(sku); + lastTurnoResultado.push({ + sku: Number(sku), + tipo, + paletes_sairam: paletes, + fardos_estimados: fpp ? paletes * fpp : 'N/D' + }); + const tr = document.createElement('tr'); + tr.innerHTML = `${sku}${tipo}${paletes}${fpp ? paletes * fpp : 'N/D'}`; + el.turnoSkuBody.appendChild(tr); + }); + + if (!Object.keys(saidaSkuTipo).length) { + const tr = document.createElement('tr'); + tr.innerHTML = 'Nenhuma saída detectada na comparação.'; + el.turnoSkuBody.appendChild(tr); + } + + saveTurnoSnapshot(rows); + + if (missing.size) { + setStatus(el.turnoStatus, `Comparação concluída (${snapshotAnterior ? 'anterior x atual' : 'estoque atual x planilha'}). SKU(s) sem fardos por palete: ${[...missing].join(', ')}.`, 'error'); + } else { + setStatus(el.turnoStatus, `Comparação concluída com sucesso (${snapshotAnterior ? 'anterior x atual' : 'estoque atual x planilha'}).`, 'success'); + } + } catch (error) { + setStatus(el.turnoStatus, `Erro ao processar planilha do turno: ${error.message}`, 'error'); + } +} + function mapImportRow(rawRow) { const row = Object.fromEntries( Object.entries(rawRow).map(([k, v]) => [String(k).trim().toLowerCase(), v]) ); + const areaRaw = row.area ?? row['área'] ?? row.endereco ?? row.endereço; + const skuRaw = row.sku ?? row.codsku ?? row['cód_sku']; + const tipoRaw = row.tipo ?? row.produto_tipo; + const paletesRaw = row.paletes ?? row.pallets ?? row.quantidade; + return { - area: normalizeText(row.area), - sku: Number(row.sku), - tipo: normalizeText(row.tipo), - paletes: Number(row.paletes), + area: normalizeAreaCode(areaRaw), + sku: Number(skuRaw), + tipo: normalizeText(tipoRaw), + paletes: Number(paletesRaw), acao: normalizeText(row.acao) }; } +function isEmptyImportItem(item) { + return !item.area && !Number.isFinite(item.sku) && !item.tipo && !Number.isFinite(item.paletes) && !item.acao; +} + function validateImportItem(item, line) { + if (isEmptyImportItem(item)) return 'SKIP'; if (!item.area) return `Linha ${line}: área inválida`; if (!Number.isFinite(item.sku) || item.sku <= 0) return `Linha ${line}: sku inválido`; if (!item.tipo) return `Linha ${line}: tipo inválido`; - if (!Number.isFinite(item.paletes) || item.paletes < 0) return `Linha ${line}: paletes inválido`; + const deleteRow = shouldDeleteByAction(item.acao); + if (!deleteRow && (!Number.isFinite(item.paletes) || item.paletes < 0)) return `Linha ${line}: paletes inválido`; return null; } +function parsePastedTable(text) { + const raw = String(text || '').trim(); + if (!raw) return []; + const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean); + if (!lines.length) return []; + + const delim = lines[0].includes(' ') ? ' ' : (lines[0].includes(';') ? ';' : ','); + const parseLine = (line) => { + if (delim === ',') { + const out = []; + let cur = ''; + let quoted = false; + for (let i = 0; i < line.length; i += 1) { + const ch = line[i]; + if (ch === '"') { + if (quoted && line[i + 1] === '"') { + cur += '"'; + i += 1; + } else quoted = !quoted; + } else if (ch === ',' && !quoted) { + out.push(cur.trim()); + cur = ''; + } else cur += ch; + } + out.push(cur.trim()); + return out; + } + return line.split(delim).map((v) => String(v || '').trim()); + }; + + const headers = parseLine(lines[0]).map((h) => normalizeText(h).toLowerCase()); + return lines.slice(1).map((line) => { + const cols = parseLine(line); + const row = {}; + headers.forEach((h, idx) => { + row[h] = cols[idx] ?? ''; + }); + return row; + }); +} + +async function processImportRows(rows, syncMode) { + if (!rows.length) throw new Error('Planilha vazia.'); + + let insertedOrUpdated = 0; + let deleted = 0; + const mentionedKeys = new Set(); + + for (let i = 0; i < rows.length; i += 1) { + const item = mapImportRow(rows[i]); + const error = validateImportItem(item, i + 2); + if (error === 'SKIP') continue; + if (error) throw new Error(error); + + if (shouldDeleteByAction(item.acao) || item.paletes === 0) { + const { error: deleteError } = await supabaseClient + .from('estoque_area') + .delete() + .match({ area: item.area, sku: item.sku, tipo: item.tipo }); + + if (deleteError) throw new Error(`Linha ${i + 2}: ${deleteError.message}`); + deleted += 1; + } else { + mentionedKeys.add(estoqueKey(item.area, item.sku, item.tipo)); + const payload = { + area: item.area, + sku: item.sku, + tipo: item.tipo, + paletes: item.paletes + }; + const { error: upsertError } = await supabaseClient.from('estoque_area').upsert(payload); + if (upsertError) throw new Error(`Linha ${i + 2}: ${upsertError.message}`); + insertedOrUpdated += 1; + } + } + + if (syncMode) { + const { data: atuais, error: fetchAtualError } = await supabaseClient + .from('estoque_area') + .select('area, sku, tipo'); + if (fetchAtualError) throw fetchAtualError; + + const paraRemover = (atuais || []).filter((row) => !mentionedKeys.has(estoqueKey(row.area, row.sku, row.tipo))); + for (const row of paraRemover) { + const { error: deleteSyncError } = await supabaseClient + .from('estoque_area') + .delete() + .match({ area: row.area, sku: row.sku, tipo: row.tipo }); + if (deleteSyncError) throw deleteSyncError; + deleted += 1; + } + } + + await loadEstoque(); + maybeAutoExport(); + return { insertedOrUpdated, deleted }; +} + async function handleImportSubmit(event) { event.preventDefault(); if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error'); @@ -328,53 +1829,40 @@ async function handleImportSubmit(event) { const workbook = XLSX.read(buffer, { type: 'array' }); const sheet = workbook.Sheets[workbook.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json(sheet, { defval: '' }); - - if (!rows.length) throw new Error('Planilha vazia.'); - - let insertedOrUpdated = 0; - let deleted = 0; - - for (let i = 0; i < rows.length; i += 1) { - const item = mapImportRow(rows[i]); - const error = validateImportItem(item, i + 2); - if (error) throw new Error(error); - - if (shouldDeleteByAction(item.acao) || item.paletes === 0) { - const { error: deleteError } = await supabaseClient - .from('estoque_area') - .delete() - .match({ area: item.area, sku: item.sku, tipo: item.tipo }); - - if (deleteError) throw new Error(`Linha ${i + 2}: ${deleteError.message}`); - deleted += 1; - } else { - const payload = { - area: item.area, - sku: item.sku, - tipo: item.tipo, - paletes: item.paletes - }; - const { error: upsertError } = await supabaseClient.from('estoque_area').upsert(payload); - if (upsertError) throw new Error(`Linha ${i + 2}: ${upsertError.message}`); - insertedOrUpdated += 1; - } - } - - await loadEstoque(); - maybeAutoExport(); - showFeedback(`Importação concluída. Incluídos/atualizados: ${insertedOrUpdated}. Apagados: ${deleted}.`); + const { insertedOrUpdated, deleted } = await processImportRows(rows, Boolean(el.importSyncMode?.checked)); + showFeedback(`Importação concluída (${el.importSyncMode?.checked ? 'sincronização agressiva' : 'modo seguro'}). Incluídos/atualizados: ${insertedOrUpdated}. Apagados: ${deleted}.`); el.importForm.reset(); + if (el.importFileName) el.importFileName.textContent = 'Nenhum arquivo selecionado'; } catch (error) { showFeedback(`Erro na importação: ${error.message}`, 'error'); } } +async function handleAdminPasteSubmit(event) { + event.preventDefault(); + if (!supabaseClient) return showFeedback('Conecte ao Supabase primeiro.', 'error'); + try { + const rows = parsePastedTable(el.adminPasteInput?.value); + const { insertedOrUpdated, deleted } = await processImportRows(rows, Boolean(el.adminPasteSyncMode?.checked)); + showFeedback(`Atualização ADM concluída (${el.adminPasteSyncMode?.checked ? 'sincronização agressiva' : 'modo seguro'}). Incluídos/atualizados: ${insertedOrUpdated}. Apagados: ${deleted}.`); + } catch (error) { + showFeedback(`Erro na atualização ADM: ${error.message}`, 'error'); + } +} + function parseAreaForLayout(areaRaw) { const area = normalizeText(areaRaw); - const normalized = area.replace(/[-_\s]/g, ''); + const normalized = normalizeAreaCode(areaRaw); + + const bSuffixedMatch = normalized.match(/^B(\d+)([ED])$/); + if (bSuffixedMatch) { + const pos = Number(bSuffixedMatch[1]); + const lado = bSuffixedMatch[2] === 'E' ? 'BE' : 'BD'; + return { bloco: lado, pos, area }; + } - const directMatch = normalized.match(/^(TISSUE|LONIL|A|C|BE|BD)(\d+)$/); + const directMatch = normalized.match(/^(TISSUE|LONIL|A|B|C|BE|BD)(\d+)$/); if (directMatch) { return { bloco: directMatch[1], pos: Number(directMatch[2]), area }; } @@ -386,53 +1874,140 @@ function parseAreaForLayout(areaRaw) { const legacyBMatch = normalized.match(/^B(\d+)$/); if (legacyBMatch) { - return { bloco: 'BE', pos: Number(legacyBMatch[1]), area }; + return { bloco: 'B', pos: Number(legacyBMatch[1]), area }; + } + + const tunelMatch = normalized.match(/^TUNEL(\d+)$/); + if (tunelMatch) { + return { bloco: 'TUNEL', pos: Number(tunelMatch[1]), area }; } return null; } +function getAreaVariants(areaCode) { + const normalized = normalizeAreaCode(areaCode); + const match = normalized.match(/^([A-Z]+)(\d+)([A-Z]?)$/); + if (!match) return [normalized]; + const prefix = match[1]; + const pos = Number(match[2]); + const suffix = match[3] || ''; + const noPad = `${prefix}${pos}${suffix}`; + const pad2 = `${prefix}${String(pos).padStart(2, '0')}${suffix}`; + return [...new Set([normalized, noPad, pad2])]; +} + +function getAreaItems(areaCode, byArea) { + const variants = getAreaVariants(areaCode); + const merged = []; + variants.forEach((key) => { + const arr = byArea.get(key); + if (arr?.length) merged.push(...arr); + }); + return merged; +} + +function getAreaText(areaCode, byArea, fallback = 'Vazio') { + const items = getAreaItems(areaCode, byArea); + if (!items.length) return fallback; + return items.map((item) => `${item.sku}`).join('; '); +} + function gerarLayoutVisual() { - if (!el.layoutGrid || !el.layoutContainer) return; + if (!el.layoutGrid || !el.layoutContainer || !el.estruturasGrid) return; el.layoutGrid.innerHTML = ''; - const colunas = ['TISSUE', 'C', 'BE', 'BD', 'A', 'LONIL']; + el.estruturasGrid.innerHTML = ''; - const parsed = cache.estoque - .map((item) => ({ item, meta: parseAreaForLayout(item.area) })) - .filter((entry) => entry.meta); + const byArea = new Map(); + cache.estoque.forEach((row) => { + const key = normalizeAreaCode(row.area); + if (!byArea.has(key)) byArea.set(key, []); + byArea.get(key).push(row); + }); + + const posicoes = Array.from({ length: 26 }, (_, i) => 26 - i); + const bPosicoes = Array.from({ length: 22 }, (_, i) => 22 - i); + + const anotacoesA = { 26: 'Bloqueado', 23: 'Bloqueado', 14: 'Recebimento', 1: 'Carregamento' }; + const anotacoesC = { 1: 'Sala ADM', 2: 'Retrabalho' }; - const maxLinha = Math.max(12, ...parsed.map((entry) => entry.meta.pos)); + const criarLinha = (prefixo, anotacoes = {}) => { + const row = document.createElement('div'); + row.className = 'bp-row'; - for (let i = 1; i <= maxLinha; i += 1) { - colunas.forEach((coluna) => { + posicoes.forEach((pos) => { + const area = `${prefixo}${String(pos).padStart(2, '0')}`; const cell = document.createElement('div'); - cell.className = 'celula vazio'; - const areaNome = `${coluna}${i}`; + cell.className = 'bp-cell'; + const fallback = manualOcupados.has(area) ? 'Ocupado' : (anotacoes[pos] || 'Vazio'); + const texto = getAreaText(area, byArea, fallback); + cell.innerHTML = `${area}
${texto}
`; + row.appendChild(cell); + }); - const itens = parsed - .filter((entry) => entry.meta.bloco === coluna && entry.meta.pos === i) - .map((entry) => entry.item); + return row; + }; - let conteudo = `${areaNome}`; - if (itens.length) { - cell.classList.remove('vazio'); - cell.classList.add('ocupado'); + const bRow = document.createElement('div'); + bRow.className = 'bp-row bp-row-b'; + Array.from({ length: 4 }).forEach(() => { + const empty = document.createElement('div'); + empty.className = 'bp-cell bp-empty'; + bRow.appendChild(empty); + }); - itens.forEach((item) => { - const tipoClass = `material-${normalizeText(item.tipo).toLowerCase()}`; - conteudo += `
SKU: ${item.sku}
${item.paletes} pal (${item.tipo})
`; - }); - } + bPosicoes.forEach((pos) => { + const areaD = `B${String(pos).padStart(2, '0')}D`; + const areaE = `B${String(pos).padStart(2, '0')}E`; + const textoD = getAreaText(areaD, byArea, manualOcupados.has(areaD) ? 'Ocupado' : 'Vazio'); + const textoE = getAreaText(areaE, byArea, manualOcupados.has(areaE) ? 'Ocupado' : 'Vazio'); + const cell = document.createElement('div'); + cell.className = 'bp-cell'; + const extra = pos === 1 ? '
Picking
' : ''; + cell.innerHTML = `${areaD} / ${areaE}
D: ${textoD}
E: ${textoE}
${extra}`; + bRow.appendChild(cell); + }); + + const blueprint = document.createElement('div'); + blueprint.className = 'blueprint-wrap'; + blueprint.appendChild(criarLinha('A', anotacoesA)); + blueprint.appendChild(bRow); + blueprint.appendChild(criarLinha('C', anotacoesC)); + el.layoutGrid.appendChild(blueprint); - cell.innerHTML = conteudo; - el.layoutGrid.appendChild(cell); + const parsed = cache.estoque + .map((item) => ({ item, meta: parseAreaForLayout(item.area) })) + .filter((entry) => entry.meta); + + const parsedEstruturas = parsed.filter((entry) => + (entry.meta.bloco === 'A' && entry.meta.pos <= 5) || entry.meta.bloco === 'TUNEL' + ); + + const totaisEstruturas = parsedEstruturas.reduce((acc, entry) => { + const chave = entry.meta.bloco === 'TUNEL' ? `TÚNEL ${entry.meta.pos}` : `A${entry.meta.pos}`; + acc[chave] = (acc[chave] || 0) + Number(entry.item.paletes || 0); + return acc; + }, {}); + + Object.entries(totaisEstruturas) + .sort(([a], [b]) => a.localeCompare(b, 'pt-BR', { numeric: true })) + .forEach(([area, caixas]) => { + const box = document.createElement('div'); + box.className = 'estrutura-box'; + box.innerHTML = `${area}${caixas} caixas`; + el.estruturasGrid.appendChild(box); }); + + if (!Object.keys(totaisEstruturas).length) { + el.estruturasGrid.innerHTML = '

Sem caixas cadastradas em Estruturas/Túnel.

'; } + renderSemanticLayout(); el.layoutContainer.classList.remove('hidden'); } + async function exportarLayoutPDF() { if (!window.html2canvas || !window.jspdf) { showFeedback('Bibliotecas de PDF não carregadas.', 'error'); @@ -461,16 +2036,25 @@ async function exportarLayoutPDF() { } -function exportPlanilhaEspelho() { - const totaisEstoque = groupTotalBySku(cache.estoque); +async function exportPlanilhaEspelho() { + if (supabaseClient) { + await loadAll(); + } + + const { rows: estoqueExportRows, missingSkus } = buildEstoqueExportRows(); + const totaisEstoque = groupTotalBySku(cache.estoque, { excludeRetrabalho: true }); const totaisExpedido = groupTotalBySku(cache.movimentacoes); exportWorkbook('planilha_espelho_wmss.xlsx', [ - { name: 'Estoque', data: cache.estoque }, + { name: 'Estoque', data: estoqueExportRows }, { name: 'Totais_SKU', data: totaisEstoque }, { name: 'Movimentacoes', data: cache.movimentacoes }, { name: 'Totais_Expedido_SKU', data: totaisExpedido } ]); + + if (missingSkus.length) { + showFeedback(`Aviso: SKU(s) sem fardos por palete cadastrado: ${missingSkus.join(', ')}.`, 'error'); + } } function maybeAutoExport() { @@ -478,6 +2062,28 @@ function maybeAutoExport() { exportPlanilhaEspelho(); } +function buildEstoqueExportRows() { + const missing = new Set(); + const rows = cache.estoque.map((row) => { + const chave = estoqueKey(row.area, row.sku, row.tipo); + const frac = fracionadoMap[chave]; + const fpp = getFardosPorPalete(row.sku); + if (!fpp) missing.add(row.sku); + const paletesIncompletos = Number(frac?.paletes_incompletos || 0); + const fardosIncompletos = Number(frac?.fardos_incompletos || 0); + const paletesContabilizados = Math.max(0, Number(row.paletes) - paletesIncompletos); + const fardosTotaisBlocado = fpp ? (paletesContabilizados * fpp) + fardosIncompletos : null; + return { + ...row, + paletes_contabilizados: paletesContabilizados, + paletes_incompletos: paletesIncompletos, + fardos_incompletos: fardosIncompletos, + fardos_totais_blocado: fardosTotaisBlocado + }; + }); + return { rows, missingSkus: [...missing] }; +} + function exportWorkbook(fileName, sheets) { const wb = XLSX.utils.book_new(); sheets.forEach(({ name, data }) => { @@ -489,22 +2095,26 @@ function exportWorkbook(fileName, sheets) { function setupExports() { el.exportCadastroBtn.addEventListener('click', () => { - const totais = groupTotalBySku(cache.estoque); + const { rows: estoqueExportRows, missingSkus } = buildEstoqueExportRows(); + const totais = groupTotalBySku(cache.estoque, { excludeRetrabalho: true }); exportWorkbook('cadastro_estoque.xlsx', [ - { name: 'Estoque', data: cache.estoque }, + { name: 'Estoque', data: estoqueExportRows }, { name: 'Totais_SKU', data: totais } ]); + if (missingSkus.length) showFeedback(`Aviso: SKU(s) sem fardos por palete: ${missingSkus.join(', ')}.`, 'error'); }); el.exportConsultaBtn.addEventListener('click', () => { - const totais = groupTotalBySku(cache.estoque); + const { rows: estoqueExportRows, missingSkus } = buildEstoqueExportRows(); + const totais = groupTotalBySku(cache.estoque, { excludeRetrabalho: true }); exportWorkbook('consulta_estoque.xlsx', [ - { name: 'Consulta_Areas', data: cache.estoque }, + { name: 'Consulta_Areas', data: estoqueExportRows }, { name: 'Totais_SKU', data: totais } ]); + if (missingSkus.length) showFeedback(`Aviso: SKU(s) sem fardos por palete: ${missingSkus.join(', ')}.`, 'error'); }); - el.exportExpedicaoBtn.addEventListener('click', () => { + el.exportExpedicaoBtn?.addEventListener('click', () => { const totaisExpedido = groupTotalBySku(cache.movimentacoes); exportWorkbook('expedicao.xlsx', [ { name: 'Expedicoes', data: cache.movimentacoes }, @@ -512,9 +2122,13 @@ function setupExports() { ]); }); - el.exportEspelhoBtn?.addEventListener('click', () => { - exportPlanilhaEspelho(); - showFeedback('Planilha espelho gerada com sucesso.'); + el.exportEspelhoBtn?.addEventListener('click', async () => { + try { + await exportPlanilhaEspelho(); + showFeedback('Planilha espelho gerada com sucesso.'); + } catch (error) { + showFeedback(`Erro ao gerar planilha espelho: ${error.message}`, 'error'); + } }); } @@ -529,20 +2143,112 @@ function setupTabs() { }); } +function setupPlanejamento() { + el.planejamentoForm?.addEventListener('submit', async (event) => { + event.preventDefault(); + let itens = parseIncomingForecast(el.previsaoEntrada?.value); + const file = el.planejamentoFile?.files?.[0]; + if (file) { + const buffer = await file.arrayBuffer(); + const workbook = XLSX.read(buffer, { type: 'array' }); + const sheet = workbook.Sheets[workbook.SheetNames[0]]; + const rows = XLSX.utils.sheet_to_json(sheet, { defval: '' }); + itens = parseIncomingForecastRows(rows); + } + const totalPrevisto = itens.reduce((acc, item) => acc + item.paletes, 0); + renderPlanejamentoTable(getPlanejamentoOcupacaoAtual(), totalPrevisto); + }); +} + +function exportConsultaResumoPDF() { + if (!window.jspdf) return showFeedback('Biblioteca de PDF não carregada.', 'error'); + const { jsPDF } = window.jspdf; + const pdf = new jsPDF('portrait'); + let y = 12; + pdf.setFontSize(13); + pdf.text('Resumo Consulta / Ocupação', 10, y); + y += 8; + pdf.setFontSize(10); + const resumo = getPaletesResumoPorSetor(); + pdf.text(`Principal: ${resumo.principal} paletes`, 10, y); y += 6; + pdf.text(`Tissue: ${resumo.tissue} paletes`, 10, y); y += 6; + pdf.text(`TDD: ${resumo.ttd} paletes`, 10, y); y += 6; + pdf.text(`Lonil: ${resumo.lonil} paletes`, 10, y); y += 8; + const totals = groupTotalBySku(filterConsultaRows(cache.estoque), { excludeRetrabalho: true }).slice(0, 20); + pdf.text('SKUs (top 20 no filtro):', 10, y); y += 6; + totals.forEach((item) => { + pdf.text(`SKU ${item.sku}: ${item.total_paletes} paletes`, 10, y); + y += 5; + if (y > 280) { pdf.addPage(); y = 12; } + }); + pdf.save('resumo_consulta_ocupacao.pdf'); +} + +function setupConsulta() { + el.consultaFilterForm?.addEventListener('change', renderConsulta); + el.mapaAreaSelect?.addEventListener('change', renderConsultaMapaHint); + el.mapaPosicaoInput?.addEventListener('input', renderConsultaMapaHint); + el.exportConsultaResumoPdfBtn?.addEventListener('click', exportConsultaResumoPDF); + document.querySelectorAll('.consulta-subnav-btn').forEach((btn) => { + btn.addEventListener('click', () => { + document.querySelectorAll('.consulta-subnav-btn').forEach((b) => b.classList.remove('active')); + document.querySelectorAll('.consulta-view').forEach((v) => v.classList.remove('active')); + btn.classList.add('active'); + const viewMap = { + geral: 'consultaViewGeral', + chao: 'consultaViewChao', + 'sku-area': 'consultaViewSkuArea', + mapa: 'consultaViewMapa' + }; + document.getElementById(viewMap[btn.dataset.consultaView])?.classList.add('active'); + }); + }); +} + +function setupOcupacao() { + el.ocupacaoForm?.addEventListener('submit', (event) => { + event.preventDefault(); + renderOcupacao(); + }); +} + function init() { setupTabs(); setupExports(); - el.connectBtn.addEventListener('click', createClient); + setupConsulta(); + setupPlanejamento(); + setupOcupacao(); + setupContagem(); + renderTurnoHistory(); + el.turnoForm?.addEventListener('submit', handleTurnoSubmit); + el.exportTurnoExcelBtn?.addEventListener('click', exportTurnoResultadoExcel); + el.exportTurnoPdfBtn?.addEventListener('click', exportTurnoResultadoPDF); + el.exportOcupacaoPdfBtn?.addEventListener('click', exportOcupacaoResumoPDF); + el.paleteIncompletoToggle?.addEventListener('change', (event) => { + el.paleteIncompletoFields?.classList.toggle('hidden', !event.target.checked); + }); el.estoqueForm.addEventListener('submit', handleEstoqueSubmit); el.produtoForm.addEventListener('submit', handleProdutoSubmit); - el.expedicaoForm.addEventListener('submit', handleExpedicaoSubmit); - el.importForm.addEventListener('submit', handleImportSubmit); + el.expedicaoForm?.addEventListener('submit', handleExpedicaoSubmit); + el.importForm?.addEventListener('submit', handleImportSubmit); + el.adminPasteForm?.addEventListener('submit', handleAdminPasteSubmit); + el.selectImportBtn?.addEventListener('click', () => el.importFile?.click()); + el.importFile?.addEventListener('change', () => { + const name = el.importFile.files?.[0]?.name || 'Nenhum arquivo selecionado'; + if (el.importFileName) el.importFileName.textContent = name; + }); el.visualizarLayoutBtn?.addEventListener('click', gerarLayoutVisual); el.exportLayoutPdfBtn?.addEventListener('click', exportarLayoutPDF); el.fecharLayoutBtn?.addEventListener('click', () => el.layoutContainer.classList.add('hidden')); + el.themeToggleBtn?.addEventListener('click', () => { + const isDark = !document.body.classList.contains('dark'); + document.body.classList.toggle('dark', isDark); + storageSet('wmss_theme', isDark ? 'dark' : 'light'); + el.themeToggleBtn.textContent = isDark ? '☀️ Modo claro' : '🌙 Modo escuro'; + }); el.autoExportToggle?.addEventListener('change', (event) => { const enabled = event.target.checked; - localStorage.setItem('wmss_auto_export', enabled ? '1' : '0'); + storageSet('wmss_auto_export', enabled ? '1' : '0'); showFeedback(enabled ? 'Auto planilha ativado.' : 'Auto planilha desativado.'); }); createClient(); diff --git a/impressao-planilha.html b/impressao-planilha.html new file mode 100644 index 0000000..3d1960a --- /dev/null +++ b/impressao-planilha.html @@ -0,0 +1,196 @@ + + + + + + WMSS • Impressão compacta de planilha + + + + + +
+
+

Impressão compacta de planilha

+

+ Página exclusiva por URL para reduzir gasto de folha. Use colunas sku e quantidade/paletes. O endereço pode vir como endereco/área/quadrante ou somente lado (direito/esquerdo) + deposito (lonil/tissue/ttd). +

+ +
+ + +
+
+
+ +
+

Prévia dos dados extraídos

+
Nenhum arquivo carregado.
+ + + + + + + +
SKUEndereçoQuantidade
Sem dados.
+
+
+ + + + diff --git a/index.html b/index.html index 32b4c95..022313c 100644 --- a/index.html +++ b/index.html @@ -14,29 +14,19 @@

Controle de Estoque por Área

Cadastro, consulta e expedição de produtos

+
-
-

Configuração Supabase

-
- - -
- -

-
+
@@ -53,6 +43,15 @@

Cadastrar produto em área

+ + +
@@ -69,25 +68,31 @@

Cadastro de produto (SKU)

-

Importar planilha (incluir / apagar posições)

-

- Colunas esperadas na planilha: area, sku, tipo, - paletes e opcional acao. -
- Se acao for APAGAR/EXCLUIR/DELETE, o registro será removido. - Caso contrário, será feito incluir/atualizar. -

-
- -
-
+

Importar planilha (cadastro)

+

Função desativada nesta tela. A operação passa a usar planilha fixa + Contagem/Conferência.

+ +
+

ADM: atualizar por colar planilha

+

Cole aqui o conteúdo copiado do Excel (com cabeçalho). Aceita colunas area, sku, tipo, paletes e opcional acao. Use para atualizar quando o upload de arquivo falhar.

+
+ + +
+ +
+
+
+

Planilha espelho automática

Ative para baixar automaticamente uma planilha atualizada sempre que houver alteração no sistema.

@@ -120,16 +125,79 @@

Estoque por área

Consulta: onde estão os produtos

+
+ + + + +
+
+ + +
+
- +
ÁreaSKUTipoQuantidade
DepósitoLadoEndereçoSKUTipoQuantidade
+
+
+
+ + + +
Tipo de chãoLadoEndereçoSKUPaletes
+
+
+
+
+ + + +
Área/DepósitoSKUPaletesFardos
+
+
+
+
+ + +
+
+
+
+ +

Totais por SKU (todas as áreas)

+

Observação: a área C01 (retrabalho) não entra na contagem de totais.

@@ -138,47 +206,199 @@

Totais por SKU (todas as áreas)

+ +
+

Sobras B01 (esquerdo/direito)

+

Lista rápida dos SKUs misturados em B01, B01E e B01D.

+
+
SKUTotal Paletes
+ + +
ÁreaSKUTipoPaletes
+
+
-

Expedir (retirar do estoque)

-
- - - +
+
+

+
+ + +
+
+ +
+

Histórico de snapshots (últimos 3)

+
+ + + +
DataRegistros
+
+
+ +
+

Saída calculada por SKU

+
+ + + +
SKUTipoPaletes saíramFardos estimados
+
+
+
+ +
+
+

Planejamento de recebimento (NF)

+

Informe previsão de chegada por SKU para estimar ocupação e blocados que precisam ser desfeitos.

+
+ + +
+ +
+
+
+
+ + + +
BlocoCapacidadeOcupadoLivreApós NFAção
+
+
+
+ +
+
+

Cálculo de ocupação do galpão

+

A ocupação agora prioriza a Contagem manual (A/B/C + Estruturas + Tissue + Lonil + TTD). Se não houver contagem preenchida, usa o sistema.

+
+
+
+

+
+ + + + + + + + + + + +
DATACap G1OCUP Terceiros G1Disponivel G1OCUPADO JSL G1DISPONIVEL G1DATACap G2OCUP Terceiros G2Disponivel G2OCUPADO JSL G2DISPONIVEL G2DATACap G3OCUP Terceiros G3Disponivel G3OCUPADO JSL G3DISPONIVEL G3DATACap G4OCUP Terceiros G4Disponivel G4 JSLOCUPADO JSL G4DISPONIVEL G4Capacidade FardosQtd Fardos
+
+
+
+ +
+
+

Contagem por rua/estrutura

+

Selecione a área. Use o botão + para adicionar mais de 1 SKU na mesma posição. Para importar planilha, o formato recomendado é: sku, deposito (principal/estruturas/lonil/tissue/ttd), quadrante (A01, B02D, chão direito/esquerdo), qtd plt e tipo plt (PL2/PBR).

+
+ + - -
+ + +
+ + + + + + + +
+

+
+ + + + + + + + + + + + + + +
+PosiçãoSKUConfirmar?Blocado no local?Prof. 1ªLarg. 1ª2ª camada?Prof. 2ªLarg. 2ª3ª camada?Paletes 3ªFardos faltando (palete não inteiro)Usar total editável?Total editávelPaletesFardos
+
-

Histórico de expedição

+

Resumo por SKU

- - +
DataSKUTipoQtd expedida
+
SKUPaletesFardos
-

diff --git a/styles.css b/styles.css index 34a690a..b72b840 100644 --- a/styles.css +++ b/styles.css @@ -1,19 +1,48 @@ :root { font-family: Inter, Arial, sans-serif; - color: #182028; - background: #f5f7fa; +} + +:root { + --bg: #f5f7fa; + --text: #182028; + --card: #ffffff; + --header: #1f4d8f; + --muted: #4a5563; + --border: #ccd4dd; + --table-border: #e1e6eb; + --btn-secondary: #51606f; + --tab: #8c96a3; +} + +body.dark { + --bg: #10151c; + --text: #e6edf3; + --card: #18202a; + --header: #0f2747; + --muted: #b8c2cf; + --border: #334152; + --table-border: #2a3442; + --btn-secondary: #3f4c5b; + --tab: #4f5c6c; } * { box-sizing: border-box; } body { margin: 0; + background: var(--bg); + color: var(--text); } header { - background: #1f4d8f; + background: var(--header); color: #fff; padding: 1.5rem; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 0.75rem; } main { @@ -23,7 +52,7 @@ main { } .card { - background: #fff; + background: var(--card); border-radius: 10px; padding: 1rem; margin-bottom: 1rem; @@ -54,10 +83,25 @@ label { input, select, button { padding: 0.55rem 0.7rem; border-radius: 8px; - border: 1px solid #ccd4dd; + border: 1px solid var(--border); font-size: 0.95rem; } +textarea { + padding: 0.55rem 0.7rem; + border-radius: 8px; + border: 1px solid var(--border); + font-size: 0.95rem; + font-family: inherit; +} + +.config-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.8rem; +} + button { background: #1f4d8f; color: #fff; @@ -65,7 +109,7 @@ button { cursor: pointer; } -button.secondary { background: #51606f; } +button.secondary { background: var(--btn-secondary); } button.danger { background: #b93636; } button.edit-btn { background: #2a7f46; margin-right: 0.35rem; } button.delete-btn { background: #b93636; } @@ -73,6 +117,8 @@ button.delete-btn { background: #b93636; } .actions { display: flex; align-items: flex-end; + gap: 0.55rem; + flex-wrap: wrap; } .tabs { @@ -81,7 +127,7 @@ button.delete-btn { background: #b93636; } margin-bottom: 1rem; } -.tab-btn { background: #8c96a3; } +.tab-btn { background: var(--tab); } .tab-btn.active { background: #1f4d8f; } .tab-content { display: none; } @@ -91,13 +137,20 @@ button.delete-btn { background: #b93636; } overflow-x: auto; } +.consulta-view { display: none; } +.consulta-view.active { display: block; } + +.ocupacao-linha-reta-wrapper table { + min-width: 1900px; +} + table { width: 100%; border-collapse: collapse; } th, td { - border-bottom: 1px solid #e1e6eb; + border-bottom: 1px solid var(--table-border); padding: 0.55rem; text-align: left; } @@ -113,11 +166,21 @@ th, td { .helper-text { margin-top: 0; - color: #4a5563; + color: var(--muted); font-size: 0.9rem; line-height: 1.4; } + +.file-picker-label input[type="file"] { + display: none; +} + +.file-name { + color: #51606f; + font-size: 0.85rem; +} + .layout-actions { display: flex; gap: 0.6rem; @@ -130,7 +193,7 @@ th, td { .layout-container { position: fixed; inset: 0; - background: #f4f6f8; + background: var(--bg); overflow: auto; padding: 1rem; z-index: 1000; @@ -141,7 +204,7 @@ th, td { justify-content: space-between; align-items: center; gap: 0.8rem; - background: #fff; + background: var(--card); border-radius: 8px; padding: 0.8rem; margin-bottom: 1rem; @@ -155,29 +218,42 @@ th, td { .hidden { display: none !important; } .layout-grid { - display: grid; - grid-template-columns: 120px repeat(4, minmax(160px, 1fr)) 120px; - gap: 8px; + overflow: auto; } -.celula { - border: 2px solid #444; - min-height: 78px; - padding: 6px; - font-size: 12px; - background: #f5f5f5; +.blueprint-wrap { + min-width: 1900px; + display: grid; + gap: 0.45rem; + background: var(--card); + padding: 0.5rem; + border-radius: 8px; } -.vazio { background: #e0e0e0; } -.ocupado { background: #90caf9; } +.bp-row { + display: grid; + grid-template-columns: repeat(26, minmax(68px, 1fr)); + gap: 2px; +} -.sku { +.bp-cell { + border: 1px dotted #666; + background: #f8f8f8; + min-height: 68px; + padding: 2px 4px; font-size: 11px; - margin-top: 4px; - border-radius: 4px; - padding: 4px; - color: #fff; - font-weight: 600; + line-height: 1.25; +} + +.bp-cell strong { + display: block; + font-size: 12px; + margin-bottom: 2px; +} + +.bp-empty { + background: transparent; + border: 0; } .material-pl2 { background: #1f4d8f; } @@ -197,3 +273,154 @@ th, td { width: 18px; height: 18px; } + + +.estruturas-section { + margin-top: 1rem; + background: #fff; + border-radius: 8px; + padding: 0.8rem; +} + +.estruturas-section h3 { + margin: 0 0 0.6rem; +} + +.estruturas-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 0.6rem; +} + +.estrutura-box { + border: 1px solid var(--border); + border-radius: 8px; + padding: 0.55rem; + display: grid; + gap: 0.25rem; + background: #f9fbfd; +} + +.estrutura-box span { + color: #3e4a58; + font-weight: 600; +} + +.semantico-section { + margin-top: 1rem; + background: #fff; + border-radius: 8px; + padding: 0.8rem; +} + +.semantico-section h3 { + margin: 0 0 0.6rem; +} + +.semantic-layout { + display: grid; + gap: 0.7rem; +} + +.sem-row { + display: grid; + gap: 0.6rem; +} + +.sem-top { + grid-template-columns: 1fr 2fr 1fr; +} + +.sem-main { + grid-template-columns: repeat(4, minmax(120px, 1fr)); +} + +.sem-tenda, +.sem-rua-top, +.sem-bloco, +.sem-servicos { + border: 1px dashed #90a0b0; + border-radius: 8px; + padding: 0.6rem; + background: #f8fafc; + text-align: center; + font-weight: 600; +} + +.sem-servicos { + display: grid; + gap: 0.35rem; +} + +.sem-foot { + color: #3e4a58; +} + +#contagemTable input[type="number"], +#contagemTable input[type="text"], +#contagemTable input:not([type]) { + width: 100%; + min-width: 72px; +} + +#contagemTable input[type="checkbox"] { + width: 18px; + height: 18px; +} + + + +#contagemTable .contagem-sku-input { + min-width: 240px; +} + +#contagemTable .contagem-collapsed { + font-size: 0.78rem; + opacity: 0.75; + white-space: nowrap; +} + +#contagemSideLabel.hidden { display: none; } +body.dark .bp-cell { background: #1f2a36; border-color: #5b6776; } +body.dark .bp-empty { background: transparent; } + + +body.dark .card, +body.dark .layout-header, +body.dark .semantico-section, +body.dark .estrutura-box, +body.dark .sem-tenda, +body.dark .sem-rua-top, +body.dark .sem-bloco, +body.dark .sem-servicos { + background: var(--card); + color: var(--text); +} + +body.dark table { color: var(--text); } +body.dark input, body.dark select, body.dark textarea { + background: #0f1721; + color: var(--text); +} + + +.contagem-plus-btn, +.contagem-remove-btn { + min-width: 32px; + padding: 0.35rem 0.5rem; + line-height: 1; +} + +.contagem-remove-btn { + background: #b93636; +} + +.contagem-posicao-sub { + opacity: 0.75; +} + +#adminPasteInput { + width: 100%; + min-height: 170px; + resize: vertical; +}