db produção
This commit is contained in:
62
src/renderer/api-helpers.js
Normal file
62
src/renderer/api-helpers.js
Normal file
@@ -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;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { extractData, fetchAllProducts } from '../api-helpers.js';
|
||||
|
||||
export async function renderClientes(container) {
|
||||
container.innerHTML = `
|
||||
<div class="page-header">
|
||||
@@ -41,31 +43,29 @@ async function loadClientes() {
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = `<div class="loading-screen"><div class="spinner"></div></div>`;
|
||||
|
||||
const [res, pRes, cRes, ptRes, pagsRes] = await Promise.all([
|
||||
window.electronAPI.get('/clients'),
|
||||
window.electronAPI.get('/products'),
|
||||
window.electronAPI.get('/comandas'),
|
||||
window.electronAPI.get('/payment-types'),
|
||||
window.electronAPI.get('/payments')
|
||||
const [res, products, cRes, ptRes, pagsRes] = await Promise.all([
|
||||
window.electronAPI.get('/clients?page=1&limit=200'),
|
||||
fetchAllProducts(),
|
||||
window.electronAPI.get('/comandas?page=1&limit=200'),
|
||||
window.electronAPI.get('/payment-types?page=1&limit=200'),
|
||||
window.electronAPI.get('/payments?page=1&limit=200')
|
||||
]);
|
||||
|
||||
if (ptRes.ok) _paymentTypes = ptRes.data;
|
||||
if (ptRes.ok) _paymentTypes = extractData(ptRes);
|
||||
|
||||
if (pRes.ok) {
|
||||
_productsMap = pRes.data.reduce((acc, p) => {
|
||||
acc[String(p.id)] = {
|
||||
name: p.name,
|
||||
price: parseFloat(p.price || 0)
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
_productsMap = (products || []).reduce((acc, p) => {
|
||||
acc[String(p.id)] = {
|
||||
name: p.name,
|
||||
price: parseFloat(p.price || 0)
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
_comandasData = cRes.ok ? (cRes.data || []) : [];
|
||||
_comandasData = cRes.ok ? extractData(cRes) : [];
|
||||
|
||||
_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);
|
||||
@@ -75,7 +75,7 @@ async function loadClientes() {
|
||||
|
||||
if (!res.ok) { wrap.innerHTML = `<div class="table-empty">Erro ao carregar clientes.</div>`; return; }
|
||||
|
||||
_clientesData = res.data || [];
|
||||
_clientesData = extractData(res);
|
||||
|
||||
_clientesData.forEach(c => {
|
||||
const fiados = _comandasData.filter(com => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { extractData, extractPagination, buildQuery, fetchAllProducts } from '../api-helpers.js';
|
||||
|
||||
function imprimirComanda(comanda, pagamentosComanda, totalComanda) {
|
||||
const itens = comanda.items || [];
|
||||
const totalPago = (pagamentosComanda || []).reduce((acc, p) => acc + parseFloat(p.value || 0), 0);
|
||||
@@ -241,13 +243,18 @@ export async function renderComandas(container) {
|
||||
</div>`;
|
||||
|
||||
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 = `<div class="loading-screen"><div class="spinner"></div></div>`;
|
||||
|
||||
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 = `<div class="table-empty">Erro ao carregar comandas.</div>`; 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 = `<div class="table-empty">Nenhuma comanda encontrada.</div>`; return; }
|
||||
|
||||
// Limita a exibição às primeiras 100 comandas
|
||||
const limitedData = data.slice(0, 100);
|
||||
if (!data.length) {
|
||||
wrap.innerHTML = `<div class="table-empty">Nenhuma comanda encontrada.</div>${renderPaginationControls()}`;
|
||||
return;
|
||||
}
|
||||
|
||||
wrap.innerHTML = `
|
||||
<table>
|
||||
@@ -365,10 +384,54 @@ function renderComandasTable(data) {
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
${data.length > 100 ? `<div style="padding:10px; text-align:center; color:var(--text-muted); font-size:0.8rem;">Exibindo apenas as últimas 100 de ${data.length} comandas.</div>` : ''}
|
||||
${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 += `<button class="btn btn-ghost btn-sm btn-pag-page" data-page="1">1</button>`;
|
||||
if (startPage > 2) pagesHtml += `<span style="color:var(--text-muted)">...</span>`;
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
const active = i === _comandaPage ? 'btn-primary btn-sm' : 'btn-ghost btn-sm';
|
||||
pagesHtml += `<button class="${active} btn-pag-page" data-page="${i}">${i}</button>`;
|
||||
}
|
||||
|
||||
if (endPage < _comandaTotalPages) {
|
||||
if (endPage < _comandaTotalPages - 1) pagesHtml += `<span style="color:var(--text-muted)">...</span>`;
|
||||
pagesHtml += `<button class="btn btn-ghost btn-sm btn-pag-page" data-page="${_comandaTotalPages}">${_comandaTotalPages}</button>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="pagination-bar" style="display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-top:1px solid var(--border);margin-top:4px">
|
||||
<span style="font-size:0.82rem;color:var(--text-muted)">Mostrando ${startItem}–${endItem} de ${_comandaTotal}</span>
|
||||
<div style="display:flex;gap:4px;align-items:center">
|
||||
<button class="btn btn-ghost btn-sm btn-pag-page" data-page="${_comandaPage - 1}" ${_comandaPage <= 1 ? 'disabled' : ''}>◀</button>
|
||||
${pagesHtml}
|
||||
<button class="btn btn-ghost btn-sm btn-pag-page" data-page="${_comandaPage + 1}" ${_comandaPage >= _comandaTotalPages ? 'disabled' : ''}>▶</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { extractData } from '../api-helpers.js';
|
||||
|
||||
export async function renderDashboard(container) {
|
||||
container.innerHTML = `
|
||||
<div class="page-header">
|
||||
@@ -37,26 +39,29 @@ export async function renderDashboard(container) {
|
||||
|
||||
// Carrega dados em paralelo
|
||||
const [mesas, comandas, pedidos, clientes] = await Promise.all([
|
||||
window.electronAPI.get('/mesas/'),
|
||||
window.electronAPI.get('/comandas/'),
|
||||
window.electronAPI.get('/orders/'),
|
||||
window.electronAPI.get('/clients/'),
|
||||
window.electronAPI.get('/mesas?page=1&limit=200'),
|
||||
window.electronAPI.get('/comandas?page=1&limit=200'),
|
||||
window.electronAPI.get('/orders?page=1&limit=200'),
|
||||
window.electronAPI.get('/clients?page=1&limit=200'),
|
||||
]);
|
||||
|
||||
const mesasData = mesas.ok ? extractData(mesas) : [];
|
||||
const comandasData = comandas.ok ? extractData(comandas) : [];
|
||||
const pedidosData = pedidos.ok ? extractData(pedidos) : [];
|
||||
const clientesData = clientes.ok ? extractData(clientes) : [];
|
||||
|
||||
if (mesas.ok) {
|
||||
// Cruza com comandas para verificar ocupação
|
||||
const mesasOcupadas = new Set();
|
||||
if (comandas.ok) {
|
||||
comandas.data.forEach(c => {
|
||||
if ((c.status === 'OPEN' || c.status === 'PAYING') && c.mesa) mesasOcupadas.add(c.mesa);
|
||||
});
|
||||
}
|
||||
comandasData.forEach(c => {
|
||||
if ((c.status === 'OPEN' || c.status === 'PAYING') && c.mesa) mesasOcupadas.add(c.mesa);
|
||||
});
|
||||
|
||||
const abertas = mesas.data.filter(m => m.active && mesasOcupadas.has(m.id)).length;
|
||||
const abertas = mesasData.filter(m => m.active && mesasOcupadas.has(m.id)).length;
|
||||
document.getElementById('stat-mesas').textContent = abertas;
|
||||
|
||||
const preview = document.getElementById('dash-mesas-preview');
|
||||
preview.innerHTML = mesas.data.slice(0, 12).map(m => {
|
||||
preview.innerHTML = mesasData.slice(0, 12).map(m => {
|
||||
const ocupada = mesasOcupadas.has(m.id);
|
||||
return `<span style="padding:4px 10px;border-radius:6px;font-size:0.78rem;font-weight:600;
|
||||
background:${ocupada ? 'rgba(239,68,68,0.15)' : 'rgba(34,197,94,0.12)'};
|
||||
@@ -67,12 +72,12 @@ export async function renderDashboard(container) {
|
||||
}
|
||||
|
||||
if (comandas.ok) {
|
||||
document.getElementById('stat-comandas').textContent = comandas.data.filter(c => c.status === 'OPEN' || c.status === 'PAYING').length;
|
||||
document.getElementById('stat-comandas').textContent = comandasData.filter(c => c.status === 'OPEN' || c.status === 'PAYING').length;
|
||||
const preview = document.getElementById('dash-comandas-preview');
|
||||
preview.innerHTML = `<table style="width:100%;font-size:0.85rem">
|
||||
<thead><tr style="color:var(--text-muted)"><th style="text-align:left;padding:4px 0">ID</th><th style="text-align:left">Nome/Mesa</th><th style="text-align:left">Status</th></tr></thead>
|
||||
<tbody>
|
||||
${comandas.data.slice(0, 5).map(c => {
|
||||
${comandasData.slice(0, 5).map(c => {
|
||||
const statusLabel = c.status === 'OPEN' ? 'Aberta' : (c.status === 'PAYING' ? 'Pagando' : 'Fechada');
|
||||
const badgeClass = c.status === 'OPEN' ? 'badge-success' : (c.status === 'PAYING' ? 'badge-warning' : 'badge-muted');
|
||||
return `
|
||||
@@ -88,11 +93,11 @@ export async function renderDashboard(container) {
|
||||
|
||||
if (pedidos.ok) {
|
||||
const hoje = new Date().toISOString().slice(0, 10);
|
||||
const pedidosHoje = pedidos.data.filter(p => (p.created_at || p.data || '').startsWith(hoje)).length;
|
||||
document.getElementById('stat-pedidos').textContent = pedidosHoje || pedidos.data.length;
|
||||
const pedidosHoje = pedidosData.filter(p => (p.created_at || p.data || '').startsWith(hoje)).length;
|
||||
document.getElementById('stat-pedidos').textContent = pedidosHoje || pedidosData.length;
|
||||
}
|
||||
|
||||
if (clientes.ok) {
|
||||
document.getElementById('stat-clientes').textContent = clientes.data.length;
|
||||
document.getElementById('stat-clientes').textContent = clientesData.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { extractData } from '../api-helpers.js';
|
||||
|
||||
export async function renderMesas(container) {
|
||||
container.innerHTML = `
|
||||
<div class="page-header">
|
||||
@@ -28,8 +30,8 @@ async function loadMesas() {
|
||||
|
||||
// Carrega mesas e comandas em paralelo para determinar ocupação
|
||||
const [mesasRes, comandasRes] = await Promise.all([
|
||||
window.electronAPI.get('/mesas'),
|
||||
window.electronAPI.get('/comandas'),
|
||||
window.electronAPI.get('/mesas?page=1&limit=200'),
|
||||
window.electronAPI.get('/comandas?page=1&limit=200'),
|
||||
]);
|
||||
|
||||
if (!mesasRes.ok) {
|
||||
@@ -37,12 +39,12 @@ async function loadMesas() {
|
||||
return;
|
||||
}
|
||||
|
||||
const mesas = mesasRes.data;
|
||||
const mesas = extractData(mesasRes);
|
||||
|
||||
// IDs de mesas com pelo menos uma comanda ativa (OPEN ou PAYING)
|
||||
const mesasOcupadas = new Set();
|
||||
if (comandasRes.ok) {
|
||||
comandasRes.data.forEach(c => {
|
||||
extractData(comandasRes).forEach(c => {
|
||||
if ((c.status === 'OPEN' || c.status === 'PAYING') && c.mesa) mesasOcupadas.add(c.mesa);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { extractData } from '../api-helpers.js';
|
||||
|
||||
export async function renderPagamentos(container) {
|
||||
const [tRes, cRes, cliRes] = await Promise.all([
|
||||
window.electronAPI.get('/payment-types'),
|
||||
window.electronAPI.get('/comandas'),
|
||||
window.electronAPI.get('/clients'),
|
||||
window.electronAPI.get('/payment-types?page=1&limit=800'),
|
||||
window.electronAPI.get('/comandas?page=1&limit=800'),
|
||||
window.electronAPI.get('/clients?page=1&limit=200'),
|
||||
]);
|
||||
const tiposPag = tRes.ok ? tRes.data : [];
|
||||
const comandas = cRes.ok ? cRes.data : [];
|
||||
const clientes = cliRes.ok ? cliRes.data : [];
|
||||
const tiposPag = tRes.ok ? extractData(tRes) : [];
|
||||
const comandas = cRes.ok ? extractData(cRes) : [];
|
||||
const clientes = cliRes.ok ? extractData(cliRes) : [];
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="page-header">
|
||||
@@ -68,10 +70,10 @@ async function loadPagamentos(tiposPag, comandas, clientes) {
|
||||
|
||||
_tiposPag = tiposPag;
|
||||
|
||||
const res = await window.electronAPI.get('/payments');
|
||||
const res = await window.electronAPI.get('/payments?page=1&limit=800');
|
||||
if (!res.ok) { wrap.innerHTML = `<div class="table-empty">Erro ao carregar pagamentos.</div>`; return; }
|
||||
|
||||
_pagsData = (res.data || []).sort((a, b) => b.id - a.id);
|
||||
_pagsData = (extractData(res) || []).sort((a, b) => b.id - a.id);
|
||||
|
||||
_cmdMap = (comandas || []).reduce((acc, c) => {
|
||||
acc[String(c.id)] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { extractData, fetchAllProducts } from '../api-helpers.js';
|
||||
|
||||
let _productsMap = {};
|
||||
let _productsNames = {};
|
||||
let _paymentTypes = [];
|
||||
@@ -85,8 +87,8 @@ export async function renderPdv(container) {
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.oninput = async (e) => {
|
||||
const pRes = await window.electronAPI.get('/products');
|
||||
const todosProdutos = pRes.ok ? pRes.data.filter(p => p.active) : [];
|
||||
const allProducts = await fetchAllProducts();
|
||||
const todosProdutos = (allProducts || []).filter(p => p.active);
|
||||
renderRight(todosProdutos, e.target.value);
|
||||
};
|
||||
}
|
||||
@@ -94,14 +96,14 @@ export async function renderPdv(container) {
|
||||
}
|
||||
|
||||
async function criarComandaPdv() {
|
||||
const mesasRes = await window.electronAPI.get('/mesas');
|
||||
const mesasRes = await window.electronAPI.get('/mesas?page=1&limit=200');
|
||||
if (mesasRes.ok) {
|
||||
_mesas = mesasRes.data;
|
||||
_mesas = extractData(mesasRes);
|
||||
}
|
||||
|
||||
const comandasRes = await window.electronAPI.get('/comandas');
|
||||
const comandasRes = await window.electronAPI.get('/comandas?page=1&limit=200');
|
||||
if (comandasRes.ok) {
|
||||
const existing = comandasRes.data.find(c => c.name === 'PDV-BALCAO' && c.status === 'OPEN');
|
||||
const existing = extractData(comandasRes).find(c => c.name === 'PDV-BALCAO' && c.status === 'OPEN');
|
||||
if (existing) {
|
||||
_pdvComanda = existing;
|
||||
console.log('[PDV] ComandaPDV encontrada:', _pdvComanda);
|
||||
@@ -135,27 +137,25 @@ async function criarComandaPdv() {
|
||||
}
|
||||
|
||||
async function loadPdvData() {
|
||||
const [pRes, ptRes, cRes] = await Promise.all([
|
||||
window.electronAPI.get('/products'),
|
||||
window.electronAPI.get('/payment-types'),
|
||||
window.electronAPI.get('/clients')
|
||||
const [products, ptRes, cRes] = await Promise.all([
|
||||
fetchAllProducts(),
|
||||
window.electronAPI.get('/payment-types?page=1&limit=200'),
|
||||
window.electronAPI.get('/clients?page=1&limit=200')
|
||||
]);
|
||||
|
||||
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;
|
||||
}, {});
|
||||
|
||||
if (ptRes.ok) _paymentTypes = ptRes.data;
|
||||
if (cRes.ok) _clients = cRes.data;
|
||||
if (ptRes.ok) _paymentTypes = extractData(ptRes);
|
||||
if (cRes.ok) _clients = extractData(cRes);
|
||||
|
||||
const todosProdutos = pRes.ok ? pRes.data.filter(p => p.active) : [];
|
||||
const todosProdutos = (products || []).filter(p => p.active);
|
||||
renderRight(todosProdutos);
|
||||
renderLeft();
|
||||
}
|
||||
@@ -257,7 +257,7 @@ async function recarregarComanda() {
|
||||
if (!_pdvComanda) return;
|
||||
const r = await window.electronAPI.get(`/comandas/${_pdvComanda.id}`);
|
||||
if (r.ok) {
|
||||
_pdvComanda = r.data;
|
||||
_pdvComanda = r.data.data || r.data;
|
||||
renderLeft();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Pedidos = Fila de Cozinha (KDS - Kitchen Display System)
|
||||
// Cada "order" representa um item individual na fila, com pipeline de status por timestamps
|
||||
|
||||
import { extractData, fetchAllProducts } from '../api-helpers.js';
|
||||
|
||||
const STATUS_CONFIG = {
|
||||
'Na fila': { badge: 'badge-warning', icon: '⏳', next: 'preparing', nextLabel: '▶ Preparando' },
|
||||
'Preparando': { badge: 'badge-info', icon: '🍳', next: 'finished', nextLabel: '✅ Pronto' },
|
||||
@@ -62,31 +64,35 @@ async function loadOrders() {
|
||||
wrap.innerHTML = `<div class="loading-screen"><div class="spinner"></div></div>`;
|
||||
|
||||
// Busca tudo em paralelo para resolver as referências (IDs)
|
||||
const [res, pRes, cRes, mRes] = await Promise.all([
|
||||
window.electronAPI.get('/orders'),
|
||||
window.electronAPI.get('/products'),
|
||||
window.electronAPI.get('/comandas'),
|
||||
window.electronAPI.get('/mesas')
|
||||
const [res, products, cRes, mRes] = await Promise.all([
|
||||
window.electronAPI.get('/orders?page=1&limit=200'),
|
||||
fetchAllProducts(),
|
||||
window.electronAPI.get('/comandas?page=1&limit=200'),
|
||||
window.electronAPI.get('/mesas?page=1&limit=200')
|
||||
]);
|
||||
|
||||
if (!res.ok) { wrap.innerHTML = `<div class="table-empty">Erro ao carregar pedidos.</div>`; return; }
|
||||
|
||||
if (pRes.ok) {
|
||||
_productsMap = pRes.data.reduce((acc, p) => { acc[String(p.id)] = p.name; return acc; }, {});
|
||||
_productsFullMap = pRes.data.reduce((acc, p) => { acc[String(p.id)] = p; return acc; }, {});
|
||||
_productsMap = (products || []).reduce((acc, p) => { acc[String(p.id)] = p.name; return acc; }, {});
|
||||
_productsFullMap = (products || []).reduce((acc, p) => { acc[String(p.id)] = p; return acc; }, {});
|
||||
if (mRes.ok) {
|
||||
const mesas = extractData(mRes);
|
||||
_mesasMap = mesas.reduce((acc, m) => { acc[String(m.id)] = m.name; return acc; }, {});
|
||||
}
|
||||
if (cRes.ok) {
|
||||
const comandas = extractData(cRes);
|
||||
_comandasMap = comandas.reduce((acc, c) => {
|
||||
acc[String(c.id)] = {
|
||||
id: c.id,
|
||||
name: c.name || '–',
|
||||
mesa: _mesasMap[String(c.mesa)] || `Mesa ${c.mesa}` || '–',
|
||||
mesa_name: c.mesa_name || _mesasMap[String(c.mesa)] || '–'
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
if (mRes.ok) _mesasMap = mRes.data.reduce((acc, m) => { acc[String(m.id)] = m.name; return acc; }, {});
|
||||
if (cRes.ok) _comandasMap = cRes.data.reduce((acc, c) => {
|
||||
acc[String(c.id)] = {
|
||||
id: c.id,
|
||||
name: c.name || '–',
|
||||
mesa: _mesasMap[String(c.mesa)] || `Mesa ${c.mesa}` || '–',
|
||||
mesa_name: c.mesa_name || _mesasMap[String(c.mesa)] || '–'
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
_ordersData = res.data;
|
||||
_ordersData = extractData(res);
|
||||
|
||||
// Por padrão, filtra para mostrar só os não entregues/cancelados
|
||||
const filtroInicial = document.getElementById('filter-status-order');
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { extractData, fetchAllProducts } from '../api-helpers.js';
|
||||
|
||||
export async function renderProdutos(container) {
|
||||
let categorias = [];
|
||||
let unidades = [];
|
||||
const [catRes, unRes] = await Promise.all([
|
||||
window.electronAPI.get('/categories'),
|
||||
window.electronAPI.get('/unit-of-measurements')
|
||||
window.electronAPI.get('/categories?page=1&limit=200'),
|
||||
window.electronAPI.get('/unit-of-measurements?page=1&limit=200')
|
||||
]);
|
||||
if (catRes.ok) categorias = catRes.data;
|
||||
if (unRes.ok) unidades = unRes.data;
|
||||
if (catRes.ok) categorias = extractData(catRes);
|
||||
if (unRes.ok) unidades = extractData(unRes);
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="page-header">
|
||||
@@ -50,9 +52,8 @@ async function loadProdutos(categorias, unidades) {
|
||||
const wrap = document.getElementById('produtos-table');
|
||||
if (!wrap) return;
|
||||
wrap.innerHTML = `<div class="loading-screen"><div class="spinner"></div></div>`;
|
||||
const res = await window.electronAPI.get('/products');
|
||||
if (!res.ok) { wrap.innerHTML = `<div class="table-empty">Erro ao carregar produtos.</div>`; return; }
|
||||
_produtosData = res.data;
|
||||
const products = await fetchAllProducts();
|
||||
_produtosData = products || [];
|
||||
renderProdutosTable(_produtosData, categorias, unidades);
|
||||
}
|
||||
|
||||
@@ -364,11 +365,9 @@ function abrirFormularioCategoria(cat, onSuccess, onListUpdate) {
|
||||
|
||||
document.getElementById('btn-cat-form-cancel').onclick = () => {
|
||||
closeModal();
|
||||
// Reabre o gerenciador
|
||||
setTimeout(() => {
|
||||
// Recarregar categorias para garantir sync
|
||||
window.electronAPI.get('/categories').then(res => {
|
||||
if (res.ok) onListUpdate(res.data);
|
||||
window.electronAPI.get('/categories?page=1&limit=200').then(res => {
|
||||
if (res.ok) onListUpdate(extractData(res));
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
@@ -386,13 +385,12 @@ function abrirFormularioCategoria(cat, onSuccess, onListUpdate) {
|
||||
|
||||
if (r.ok) {
|
||||
showToast(isEdit ? 'Categoria atualizada!' : 'Categoria criada!', 'success');
|
||||
const res = await window.electronAPI.get('/categories');
|
||||
const res = await window.electronAPI.get('/categories?page=1&limit=200');
|
||||
if (res.ok) {
|
||||
onListUpdate(res.data);
|
||||
const cats = extractData(res);
|
||||
onListUpdate(cats);
|
||||
closeModal();
|
||||
// Não reabre o gerenciador imediatamente para dar tempo do toast sumir,
|
||||
// mas aqui vamos reabrir para manter o fluxo
|
||||
setTimeout(() => abrirModalGerenciarCategorias(res.data), 300);
|
||||
setTimeout(() => abrirModalGerenciarCategorias(cats), 300);
|
||||
}
|
||||
} else {
|
||||
showToast(r.error, 'error');
|
||||
|
||||
Reference in New Issue
Block a user