From eb62b890cf834f631282143ffda7855c9f458c31 Mon Sep 17 00:00:00 2001 From: Welton Moura Date: Thu, 30 Apr 2026 15:34:54 -0300 Subject: [PATCH] =?UTF-8?q?db=20produ=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/api-helpers.js | 62 +++++++++++ src/renderer/pages/clientes.js | 38 +++---- src/renderer/pages/comandas.js | 181 ++++++++++++++++++++----------- src/renderer/pages/dashboard.js | 37 ++++--- src/renderer/pages/mesas.js | 10 +- src/renderer/pages/pagamentos.js | 18 +-- src/renderer/pages/pdv.js | 48 ++++---- src/renderer/pages/pedidos.js | 44 ++++---- src/renderer/pages/produtos.js | 30 +++-- 9 files changed, 300 insertions(+), 168 deletions(-) create mode 100644 src/renderer/api-helpers.js diff --git a/src/renderer/api-helpers.js b/src/renderer/api-helpers.js new file mode 100644 index 0000000..2921f31 --- /dev/null +++ b/src/renderer/api-helpers.js @@ -0,0 +1,62 @@ +/** + * API helpers for handling paginated responses from Go server. + * + * Server now returns: { data: [...], total, page, limit, total_pages } + * Max limit per page: 200 + */ + +const MAX_PAGE_SIZE = 200; + +export function buildQuery(params = {}) { + const parts = []; + for (const [key, value] of Object.entries(params)) { + if (value !== null && value !== undefined && value !== '') { + parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); + } + } + return parts.length ? `?${parts.join('&')}` : ''; +} + +export function extractData(res) { + if (!res.ok) return []; + const body = res.data; + if (Array.isArray(body)) return body; + if (body && Array.isArray(body.data)) return body.data; + return []; +} + +export function extractPagination(res) { + if (!res.ok || !res.data) return { total: 0, page: 1, limit: 50, total_pages: 1 }; + const body = res.data; + if (body && typeof body === 'object' && 'total' in body) { + return { + total: body.total || 0, + page: body.page || 1, + limit: body.limit || 50, + total_pages: body.total_pages || 1 + }; + } + const arr = Array.isArray(body) ? body : (body.data || []); + return { total: arr.length, page: 1, limit: arr.length, total_pages: 1 }; +} + +export async function fetchAllProducts() { + let allData = []; + let page = 1; + let totalPages = 1; + + while (page <= totalPages) { + const params = { page, limit: MAX_PAGE_SIZE }; + const res = await window.electronAPI.get(`/products${buildQuery(params)}`); + if (!res.ok) break; + + allData = allData.concat(extractData(res)); + const meta = extractPagination(res); + totalPages = meta.total_pages; + page++; + + if (allData.length >= meta.total) break; + } + + return allData; +} diff --git a/src/renderer/pages/clientes.js b/src/renderer/pages/clientes.js index 992640c..175bea5 100644 --- a/src/renderer/pages/clientes.js +++ b/src/renderer/pages/clientes.js @@ -1,3 +1,5 @@ +import { extractData, fetchAllProducts } from '../api-helpers.js'; + export async function renderClientes(container) { container.innerHTML = ` `; let mesas = []; - const mesasRes = await window.electronAPI.get('/mesas'); - if (mesasRes.ok) mesas = mesasRes.data; + const mesasRes = await window.electronAPI.get('/mesas?page=1&limit=200'); + if (mesasRes.ok) mesas = extractData(mesasRes); await loadComandas(mesas); document.getElementById('btn-nova-comanda').addEventListener('click', () => abrirModalNovaComanda(mesas)); - document.getElementById('search-comanda').addEventListener('input', () => filtrarComandas()); + + let searchTimeout; + document.getElementById('search-comanda').addEventListener('input', () => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => filtrarComandas(), 300); + }); document.getElementById('filter-status').addEventListener('change', () => filtrarComandas()); } @@ -258,38 +265,48 @@ let _productsNames = {}; // Cache de nomes {id: name} let _paymentTypes = []; let _clients = []; let _paymentsMap = {}; // Cache de pagamentos por comanda {comandaId: [pagamentos]} +let _comandaPage = 1; +let _comandaTotal = 0; +let _comandaTotalPages = 1; +const COMANDA_PAGE_SIZE = 50; -async function loadComandas(mesas) { +async function loadComandas(mesas, page = 1) { _mesasRef = mesas; + _comandaPage = page; const wrap = document.getElementById('comandas-table'); if (!wrap) return; wrap.innerHTML = `
`; - const [res, pRes, ptRes, cRes, pagsRes] = await Promise.all([ - window.electronAPI.get('/comandas'), - window.electronAPI.get('/products'), - window.electronAPI.get('/payment-types'), - window.electronAPI.get('/clients'), - window.electronAPI.get('/payments') + const q = document.getElementById('search-comanda')?.value.toLowerCase() || ''; + const status = document.getElementById('filter-status')?.value || ''; + + const queryParams = { page, limit: COMANDA_PAGE_SIZE }; + if (q) queryParams.name = q; + if (status) queryParams.status = status === 'ACTIVE' ? 'OPEN' : status; + + const [res, ptRes, cRes, pagsRes, products] = await Promise.all([ + window.electronAPI.get(`/comandas${buildQuery(queryParams)}`), + window.electronAPI.get('/payment-types?page=1&limit=200'), + window.electronAPI.get('/clients?page=1&limit=200'), + window.electronAPI.get('/payments?page=1&limit=200'), + fetchAllProducts() ]); - if (ptRes.ok) _paymentTypes = ptRes.data; - if (cRes.ok) _clients = cRes.data; + if (ptRes.ok) _paymentTypes = extractData(ptRes); + if (cRes.ok) _clients = extractData(cRes); - if (pRes.ok) { - _productsMap = pRes.data.reduce((acc, p) => { - acc[String(p.id)] = parseFloat(p.price || 0); - return acc; - }, {}); - _productsNames = pRes.data.reduce((acc, p) => { - acc[String(p.id)] = p.name || `Produto #${p.id}`; - return acc; - }, {}); - } + _productsMap = (products || []).reduce((acc, p) => { + acc[String(p.id)] = parseFloat(p.price || 0); + return acc; + }, {}); + _productsNames = (products || []).reduce((acc, p) => { + acc[String(p.id)] = p.name || `Produto #${p.id}`; + return acc; + }, {}); _paymentsMap = {}; if (pagsRes.ok) { - (pagsRes.data || []).forEach(p => { + (extractData(pagsRes) || []).forEach(p => { if (p.comanda) { if (!_paymentsMap[p.comanda]) _paymentsMap[p.comanda] = []; _paymentsMap[p.comanda].push(p); @@ -298,20 +315,22 @@ async function loadComandas(mesas) { } if (!res.ok) { wrap.innerHTML = `
Erro ao carregar comandas.
`; return; } - _comandasData = res.ok ? res.data : []; - _comandasData.reverse(); + _comandasData = extractData(res); + const meta = extractPagination(res); + _comandaTotal = meta.total; + _comandaTotalPages = meta.total_pages; console.log('[PDV] Comandas carregadas do servidor:', _comandasData); - filtrarComandas(); + renderComandasTable(_comandasData); } function renderComandasTable(data) { const wrap = document.getElementById('comandas-table'); if (!wrap) return; - if (!data.length) { wrap.innerHTML = `
Nenhuma comanda encontrada.
`; return; } - - // Limita a exibição às primeiras 100 comandas - const limitedData = data.slice(0, 100); + if (!data.length) { + wrap.innerHTML = `
Nenhuma comanda encontrada.
${renderPaginationControls()}`; + return; + } wrap.innerHTML = ` @@ -365,10 +384,54 @@ function renderComandasTable(data) { }).join('')}
- ${data.length > 100 ? `
Exibindo apenas as últimas 100 de ${data.length} comandas.
` : ''} + ${renderPaginationControls()} `; - // Listener para linha toda + bindComandaTableListeners(); +} + +function renderPaginationControls() { + if (_comandaTotalPages <= 1) return ''; + + const startItem = (_comandaPage - 1) * COMANDA_PAGE_SIZE + 1; + const endItem = Math.min(_comandaPage * COMANDA_PAGE_SIZE, _comandaTotal); + + let pagesHtml = ''; + const maxVisible = 5; + let startPage = Math.max(1, _comandaPage - Math.floor(maxVisible / 2)); + let endPage = Math.min(_comandaTotalPages, startPage + maxVisible - 1); + if (endPage - startPage < maxVisible - 1) startPage = Math.max(1, endPage - maxVisible + 1); + + if (startPage > 1) { + pagesHtml += ``; + if (startPage > 2) pagesHtml += `...`; + } + + for (let i = startPage; i <= endPage; i++) { + const active = i === _comandaPage ? 'btn-primary btn-sm' : 'btn-ghost btn-sm'; + pagesHtml += ``; + } + + if (endPage < _comandaTotalPages) { + if (endPage < _comandaTotalPages - 1) pagesHtml += `...`; + pagesHtml += ``; + } + + return ` +
+ Mostrando ${startItem}–${endItem} de ${_comandaTotal} +
+ + ${pagesHtml} + +
+
`; +} + +function bindComandaTableListeners() { + const wrap = document.getElementById('comandas-table'); + if (!wrap) return; + wrap.querySelectorAll('.comanda-row').forEach(row => { row.addEventListener('click', () => { const comanda = _comandasData.find(c => c.id === parseInt(row.dataset.id)); @@ -376,7 +439,6 @@ function renderComandasTable(data) { }); }); - // Listener para Receber wrap.querySelectorAll('.btn-receber').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); @@ -385,7 +447,6 @@ function renderComandasTable(data) { }); }); - // Listener para Editar wrap.querySelectorAll('.btn-editar').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); @@ -394,25 +455,32 @@ function renderComandasTable(data) { }); }); - // Listener para botão "Pagar" (muda p/ PAYING) wrap.querySelectorAll('.btn-pagar').forEach(btn => { btn.addEventListener('click', async (e) => { e.stopPropagation(); const r = await window.electronAPI.patch(`/comandas/${btn.dataset.id}`, { status: 'PAYING' }); - if (r.ok) { showToast('Comanda em fase de pagamento!', 'info'); loadComandas(_mesasRef); } + if (r.ok) { showToast('Comanda em fase de pagamento!', 'info'); loadComandas(_mesasRef, _comandaPage); } else showToast(r.error, 'error'); }); }); - // Listener para botão "Reabrir" (muda p/ OPEN) wrap.querySelectorAll('.btn-reopen').forEach(btn => { btn.addEventListener('click', async (e) => { e.stopPropagation(); const r = await window.electronAPI.patch(`/comandas/${btn.dataset.id}`, { status: 'OPEN' }); - if (r.ok) { showToast('Comanda reaberta!', 'info'); loadComandas(_mesasRef); } + if (r.ok) { showToast('Comanda reaberta!', 'info'); loadComandas(_mesasRef, _comandaPage); } else showToast(r.error, 'error'); }); }); + + wrap.querySelectorAll('.btn-pag-page').forEach(btn => { + btn.addEventListener('click', () => { + const page = parseInt(btn.dataset.page); + if (page >= 1 && page <= _comandaTotalPages && page !== _comandaPage) { + loadComandas(_mesasRef, page); + } + }); + }); } async function abrirModalPagamento(comanda, onPaymentComplete) { try { @@ -525,7 +593,7 @@ async function abrirModalPagamento(comanda, onPaymentComplete) { showToast(isVale ? 'Venda registrada como FIADO!' : 'Pagamento realizado!', 'success'); } closeModal(); - loadComandas(_mesasRef); + loadComandas(_mesasRef, _comandaPage); if (onPaymentComplete) onPaymentComplete(); } catch (err) { showToast(err.message, 'error'); @@ -542,18 +610,7 @@ async function abrirModalPagamento(comanda, onPaymentComplete) { } function filtrarComandas() { - const q = document.getElementById('search-comanda')?.value.toLowerCase() || ''; - const status = document.getElementById('filter-status')?.value || ''; - const filtered = _comandasData.filter(c => { - const matchQ = !q || - (c.name || '').toLowerCase().includes(q) || - (c.mesa_name || '').toLowerCase().includes(q) || - String(c.id).includes(q); - const matchStatus = !status || - (status === 'ACTIVE' ? (c.status === 'OPEN' || c.status === 'PAYING') : (c.status === status)); - return matchQ && matchStatus; - }); - renderComandasTable(filtered); + loadComandas(_mesasRef, 1); } // ─── Modal de Itens (Novo Layout PDV Split) ─────────────────────────────────── @@ -569,11 +626,11 @@ async function abrirItensComanda(comandaIdOrObj) { const podeAdd = comanda.status === 'OPEN'; // Carrega produtos (ativos) e usuário logado - const [pRes, loggedUser] = await Promise.all([ - window.electronAPI.get('/products'), + const [allProducts, loggedUser] = await Promise.all([ + fetchAllProducts(), window.electronAPI.getUser() ]); - let todosProdutos = pRes.ok ? pRes.data.filter(p => p.active) : []; + let todosProdutos = (allProducts || []).filter(p => p.active); openModal({ full: true, @@ -604,9 +661,9 @@ async function abrirItensComanda(comandaIdOrObj) { // Carrega pagamentos específicos desta comanda let pagamentosComanda = _paymentsMap[comanda.id] || []; if (!pagamentosComanda.length) { - const pagsRes = await window.electronAPI.get('/payments'); + const pagsRes = await window.electronAPI.get(`/payments?page=1&limit=200&comanda_id=${comanda.id}`); if (pagsRes.ok) { - pagamentosComanda = (pagsRes.data || []).filter(p => String(p.comanda) === String(comanda.id)); + pagamentosComanda = (extractData(pagsRes) || []).filter(p => String(p.comanda) === String(comanda.id)); } } @@ -731,7 +788,7 @@ async function abrirItensComanda(comandaIdOrObj) { showToast('Item excluído!', 'success'); comanda.items = comanda.items.filter(it => it.id !== parseInt(itemId)); renderLeft(); - loadComandas(_mesasRef); + loadComandas(_mesasRef, _comandaPage); } else { showToast(r.error, 'error'); } @@ -764,9 +821,9 @@ async function abrirItensComanda(comandaIdOrObj) { if (novaObs === null || novaObs === item.obs) return; // Busca os pedidos para encontrar o ID da order vinculada - const ordersRes = await window.electronAPI.get('/orders'); + const ordersRes = await window.electronAPI.get(`/orders?page=1&limit=200&comanda_id=${comanda.id}`); if (ordersRes.ok) { - const linkedOrder = ordersRes.data.find(o => String(o.productComanda) === itemId); + const linkedOrder = extractData(ordersRes).find(o => String(o.productComanda) === itemId); if (linkedOrder) { // PATCH na Order (Cozinha) @@ -815,7 +872,7 @@ async function abrirItensComanda(comandaIdOrObj) { if (r.ok) { showToast('Comanda excluída!', 'success'); closeModal(); - loadComandas(_mesasRef); + loadComandas(_mesasRef, _comandaPage); } else { showToast(r.error, 'error'); } @@ -833,7 +890,7 @@ async function abrirItensComanda(comandaIdOrObj) { comanda.items.push(novoItem); renderLeft(); - loadComandas(_mesasRef); + loadComandas(_mesasRef, _comandaPage); if (prod && prod.cuisine) { const orderPayload = { @@ -999,7 +1056,7 @@ function abrirModalNovaComanda(mesas, comandaExistente = null) { if (r.ok) { showToast(isEdit ? 'Comanda atualizada!' : 'Comanda criada!', 'success'); closeModal(); - loadComandas(_mesasRef); + loadComandas(_mesasRef, 1); if (!isEdit) { setTimeout(() => abrirItensComanda(r.data), 300); } diff --git a/src/renderer/pages/dashboard.js b/src/renderer/pages/dashboard.js index 3c39bee..9872ad2 100644 --- a/src/renderer/pages/dashboard.js +++ b/src/renderer/pages/dashboard.js @@ -1,3 +1,5 @@ +import { extractData } from '../api-helpers.js'; + export async function renderDashboard(container) { container.innerHTML = `