filtro em produtos, bug tela de preparo, nome do user nas comandas e itens

This commit is contained in:
2026-07-23 12:50:26 -03:00
parent 14a36b68b5
commit 14e92c91fd
8 changed files with 407 additions and 31 deletions

View File

@@ -59,15 +59,19 @@ export async function renderKitchen(container: HTMLElement) {
let currentStatus = 'PENDENTE';
let currentFilter = '';
let autoRefreshInterval: ReturnType<typeof setInterval> | null = null;
let requestId = 0;
const loadItems = async () => {
const grid = document.getElementById('items-grid')!;
grid.innerHTML = '<p class="card-subtitle">Carregando...</p>';
const myRequestId = ++requestId;
try {
let url = `/painel/itens?status=${currentStatus}`;
if (currentFilter !== '') url += `&itemCozinha=${currentFilter}`;
const items = await api.get(url);
if (myRequestId !== requestId) return;
const currentIds = new Set<string>(items.map((i: any) => i.id));
const previousIds = previousItemIdsByStatus.get(currentStatus);
@@ -106,9 +110,10 @@ export async function renderKitchen(container: HTMLElement) {
</div>
<h3 class="card-title">${item.quantidade}× ${item.produto.nome}</h3>
${item.observacao ? `<p style="color:var(--warning);font-size:0.875rem;margin-bottom:1rem;">⚠️ ${item.observacao}</p>` : ''}
<p class="card-subtitle" style="margin-bottom:1.5rem;">
<p class="card-subtitle" style="margin-bottom:0.5rem;">
Pedido às ${new Date(item.criadoEm).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}
</p>
${item.usuario ? `<p style="color:var(--text-secondary);font-size:0.8rem;margin-bottom:1rem;">Adicionado por: <strong>${item.usuario.nome}</strong></p>` : '<p style="margin-bottom:1rem;"></p>'}
${action.label ? `
<button class="btn" style="width:100%;" data-action="advance" data-item-id="${item.id}" data-next-status="${action.next}">
${action.label}

View File

@@ -87,6 +87,10 @@ export async function renderMenu(container: HTMLElement) {
`;
let categorias: any[] = [];
let allProducts: any[] = [];
let filterCategoria = '';
let filterAtivo = '';
let filterCozinha = '';
const loadCategorias = async () => {
categorias = await api.get('/categorias');
@@ -96,47 +100,109 @@ export async function renderMenu(container: HTMLElement) {
const renderTabProdutos = async () => {
const tc = document.getElementById('tab-content')!;
const products = await api.get('/produtos');
allProducts = await api.get('/produtos');
tc.innerHTML = `
<div class="page-header" style="margin-bottom:1rem;">
<span class="card-subtitle">${products.length} produto(s) cadastrado(s)</span>
<span class="card-subtitle" id="produtos-count"></span>
<button class="btn" id="btn-novo-produto"> Novo Produto</button>
</div>
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:1.5rem;align-items:flex-end;">
<div class="form-group" style="margin:0;width:auto;">
<label style="color:var(--text-secondary);font-size:0.8rem;">Categoria</label>
<select id="f-categoria" class="form-control" style="padding:0.4rem 0.75rem;">
<option value="">Todas</option>
</select>
</div>
<div class="form-group" style="margin:0;width:auto;">
<label style="color:var(--text-secondary);font-size:0.8rem;">Status</label>
<select id="f-ativo" class="form-control" style="padding:0.4rem 0.75rem;">
<option value="">Todos</option>
<option value="true">Ativos</option>
<option value="false">Inativos</option>
</select>
</div>
<div class="form-group" style="margin:0;width:auto;">
<label style="color:var(--text-secondary);font-size:0.8rem;">Tipo</label>
<select id="f-cozinha" class="form-control" style="padding:0.4rem 0.75rem;">
<option value="">Todos</option>
<option value="true">🍳 Cozinha</option>
<option value="false">🍹 Bar</option>
</select>
</div>
<button class="btn btn-secondary btn-sm" id="btn-clear-filters" style="margin-bottom:0;">✕ Limpar</button>
</div>
<div class="table-wrap">
<table>
<thead>
<tr><th>Nome</th><th>Categoria</th><th>Preço</th><th>Estoque</th><th>Tipo</th><th>Status</th><th>Ações</th></tr>
</thead>
<tbody>
${products.map((p: any) => `
<tr>
<td data-label="Produto"><strong>${p.nome}</strong>${p.descricao ? `<br><small style="color:var(--text-secondary)">${p.descricao}</small>` : ''}</td>
<td data-label="Categoria">${p.categoria?.nome ?? '—'}</td>
<td data-label="Preço">${formatBRL(p.preco)}</td>
<td data-label="Estoque">
<span style="color:${p.estoqueAtual <= p.estoqueMinimo ? 'var(--accent-primary)' : 'var(--text-primary)'}">
${p.estoqueAtual} ${p.estoqueAtual <= p.estoqueMinimo ? '⚠️' : ''}
</span>
</td>
<td data-label="Tipo"><span class="badge ${p.itemCozinha ? 'badge-cozinha' : 'badge-barman'}">${p.itemCozinha ? 'Cozinha' : 'Bar'}</span></td>
<td data-label="Status"><span class="badge ${p.ativo ? 'badge-success' : 'badge-warning'}">${p.ativo ? 'Ativo' : 'Inativo'}</span></td>
<td data-label="Ações" style="display:flex;gap:0.5rem;flex-wrap:wrap;">
<button class="btn btn-sm btn-secondary" onclick="window.editProduct(${JSON.stringify(p).replace(/"/g, '&quot;')})">✏️</button>
<button class="btn btn-sm btn-danger" onclick="window.deleteProduct('${p.id}','${p.nome}')">🗑</button>
</td>
</tr>
`).join('')}
</tbody>
<tbody id="produtos-tbody"></tbody>
</table>
</div>
`;
const catSel = document.getElementById('f-categoria') as HTMLSelectElement;
catSel.innerHTML = '<option value="">Todas</option>' +
categorias.map((c: any) => `<option value="${c.id}">${c.nome}</option>`).join('');
catSel.value = filterCategoria;
(document.getElementById('f-ativo') as HTMLSelectElement).value = filterAtivo;
(document.getElementById('f-cozinha') as HTMLSelectElement).value = filterCozinha;
catSel.addEventListener('change', () => { filterCategoria = catSel.value; renderProductsTable(); });
(document.getElementById('f-ativo') as HTMLSelectElement).addEventListener('change', (e) => { filterAtivo = (e.target as HTMLSelectElement).value; renderProductsTable(); });
(document.getElementById('f-cozinha') as HTMLSelectElement).addEventListener('change', (e) => { filterCozinha = (e.target as HTMLSelectElement).value; renderProductsTable(); });
document.getElementById('btn-clear-filters')?.addEventListener('click', () => {
filterCategoria = '';
filterAtivo = '';
filterCozinha = '';
(document.getElementById('f-categoria') as HTMLSelectElement).value = '';
(document.getElementById('f-ativo') as HTMLSelectElement).value = '';
(document.getElementById('f-cozinha') as HTMLSelectElement).value = '';
renderProductsTable();
});
renderProductsTable();
document.getElementById('btn-novo-produto')?.addEventListener('click', () => {
clearProductForm();
(document.getElementById('modal-produto') as HTMLElement).style.display = 'flex';
});
};
const renderProductsTable = () => {
let filtered = allProducts;
if (filterCategoria) filtered = filtered.filter((p: any) => p.categoriaId === filterCategoria);
if (filterAtivo !== '') filtered = filtered.filter((p: any) => String(p.ativo) === filterAtivo);
if (filterCozinha !== '') filtered = filtered.filter((p: any) => String(p.itemCozinha) === filterCozinha);
const countEl = document.getElementById('produtos-count');
if (countEl) countEl.textContent = `${filtered.length} de ${allProducts.length} produto(s)`;
const tbody = document.getElementById('produtos-tbody')!;
if (!filtered.length) {
tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;padding:2rem;color:var(--text-secondary);">Nenhum produto encontrado com os filtros selecionados.</td></tr>`;
return;
}
tbody.innerHTML = filtered.map((p: any) => `
<tr>
<td data-label="Produto"><strong>${p.nome}</strong>${p.descricao ? `<br><small style="color:var(--text-secondary)">${p.descricao}</small>` : ''}</td>
<td data-label="Categoria">${p.categoria?.nome ?? '—'}</td>
<td data-label="Preço">${formatBRL(p.preco)}</td>
<td data-label="Estoque">
<span style="color:${p.estoqueAtual <= p.estoqueMinimo ? 'var(--accent-primary)' : 'var(--text-primary)'}">
${p.estoqueAtual} ${p.estoqueAtual <= p.estoqueMinimo ? '⚠️' : ''}
</span>
</td>
<td data-label="Tipo"><span class="badge ${p.itemCozinha ? 'badge-cozinha' : 'badge-barman'}">${p.itemCozinha ? 'Cozinha' : 'Bar'}</span></td>
<td data-label="Status"><span class="badge ${p.ativo ? 'badge-success' : 'badge-warning'}">${p.ativo ? 'Ativo' : 'Inativo'}</span></td>
<td data-label="Ações" style="display:flex;gap:0.5rem;flex-wrap:wrap;">
<button class="btn btn-sm btn-secondary" onclick="window.editProduct(${JSON.stringify(p).replace(/"/g, '&quot;')})">✏️</button>
<button class="btn btn-sm btn-danger" onclick="window.deleteProduct('${p.id}','${p.nome}')">🗑</button>
</td>
</tr>
`).join('');
};
const renderTabCategorias = async () => {
const tc = document.getElementById('tab-content')!;
const cats = await api.get('/categorias');

View File

@@ -108,6 +108,7 @@ export async function renderOrders(container: HTMLElement) {
<span class="detalhes-summary-client" id="detalhes-label-client"></span>
</div>
<div id="detalhes-label-status"></div>
<div id="detalhes-label-usuario" style="font-size:0.8rem;color:var(--text-secondary);margin-top:0.25rem;"></div>
</div>
<div class="detalhes-edit-section">
@@ -175,6 +176,7 @@ export async function renderOrders(container: HTMLElement) {
<th style="text-align:center;">Qtd</th>
<th style="text-align:right;">Subtotal</th>
<th>Obs</th>
<th>Adicionado por</th>
<th>Status</th>
<th>Ações</th>
</tr>
@@ -294,6 +296,7 @@ async function loadComandas(params?: { status?: string; dataInicio?: string; dat
<tr>
<th>Identificador</th>
<th>Cliente</th>
<th>Aberto por</th>
<th>Status</th>
<th>Total</th>
<th>Aberta em</th>
@@ -305,6 +308,7 @@ async function loadComandas(params?: { status?: string; dataInicio?: string; dat
<tr>
<td data-label="Mesa" class="cmd-ident"><strong>${c.identificador}</strong></td>
<td data-label="Cliente" class="cmd-client">${c.nomeCliente ?? ''}</td>
<td data-label="Aberto por" class="cmd-usuario" style="color:var(--text-secondary);font-size:0.85rem;">${c.usuario?.nome ?? '—'}</td>
<td data-label="Status" class="cmd-status"><span class="badge status-${c.status.toLowerCase()}">${c.status}</span></td>
<td data-label="Total" class="cmd-total"><strong>${formatBRL(calcularTotalFinal(c))}</strong></td>
<td data-label="Aberta em" class="cmd-data">${new Date(c.criadoEm).toLocaleString('pt-BR')}</td>
@@ -606,6 +610,12 @@ function bindOrderEvents() {
document.getElementById('detalhes-label-ident')!.textContent = c.identificador;
document.getElementById('detalhes-label-client')!.textContent = c.nomeCliente ? `· ${c.nomeCliente}` : '';
document.getElementById('detalhes-label-status')!.innerHTML = statusBadge(c.status);
// Exibir quem abriu a comanda
const detalhesUsuario = document.getElementById('detalhes-label-usuario');
if (detalhesUsuario) {
detalhesUsuario.textContent = c.usuario ? `Aberto por: ${c.usuario.nome}` : '';
}
// Render itens
const tbody = document.getElementById('detalhes-itens-tbody')!;
@@ -614,7 +624,7 @@ function bindOrderEvents() {
const canDelete = user && ['ADMIN', 'CAIXA'].includes(user.role) && c.status === 'ABERTA';
if (itens.length === 0) {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;color:var(--text-secondary);">Nenhum item adicionado</td></tr>`;
tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;color:var(--text-secondary);">Nenhum item adicionado</td></tr>`;
} else {
tbody.innerHTML = itens.map((i: any) => `
<tr>
@@ -627,6 +637,7 @@ function bindOrderEvents() {
: (i.observacao ?? '')
}
</td>
<td data-label="Adicionado por" class="item-usuario" style="font-size:0.8rem;color:var(--text-secondary);">${i.usuario?.nome ?? '—'}</td>
<td data-label="Status" class="item-status"><span class="badge badge-${i.status === 'ENTREGUE' ? 'success' : 'warning'}">${i.status}</span></td>
<td data-label="Ações" class="item-actions">
${canDelete ? `