- Backend: Fastify + Prisma/SQLite com autenticacao JWT - CRUD completo: usuarios, categorias, produtos, comandas - Sistema de comandas com itens, status, pagamento - Controle de estoque (entrada/saida automatica) - Relatorios e dashboard com KPIs - Notificacoes sonoras na cozinha - Frontend: SPA vanilla TS com tema dark glassmorphism - Mobile-first com bottom-sheet e cards compactos - Busca de comandas por periodo - Modal de detalhes com secao editavel colapsavel
157 lines
7.0 KiB
TypeScript
157 lines
7.0 KiB
TypeScript
import { api } from '../api';
|
||
import { renderNavbar, toast, roleBadge } from '../utils';
|
||
|
||
const ROLE_OPTIONS = ['ADMIN', 'CAIXA', 'GARCOM', 'COZINHA', 'BARMAN'];
|
||
|
||
export async function renderUsers(container: HTMLElement) {
|
||
container.innerHTML = `
|
||
${renderNavbar('/users')}
|
||
<div class="container animate-fade-in">
|
||
<div class="page-header">
|
||
<h1>Equipe 👥</h1>
|
||
<button class="btn" id="btn-novo-usuario">+ Novo Usuário</button>
|
||
</div>
|
||
<div id="users-list"><p class="card-subtitle">Carregando...</p></div>
|
||
</div>
|
||
|
||
<!-- Modal Usuário -->
|
||
<div id="modal-usuario" class="modal-overlay" style="display:none;">
|
||
<div class="modal">
|
||
<div class="modal-header">
|
||
<h3 id="modal-user-title">Novo Usuário</h3>
|
||
<button class="btn-icon" onclick="document.getElementById('modal-usuario').style.display='none'">✕</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="form-usuario">
|
||
<input type="hidden" id="user-id">
|
||
<div class="form-group">
|
||
<label>Nome Completo *</label>
|
||
<input type="text" id="u-nome" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>E-mail *</label>
|
||
<input type="email" id="u-email" class="form-control" required>
|
||
</div>
|
||
<div class="form-group" id="senha-group">
|
||
<label>Senha *</label>
|
||
<input type="password" id="u-senha" class="form-control" placeholder="Mínimo 6 caracteres">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Cargo (Role) *</label>
|
||
<select id="u-role" class="form-control" required>
|
||
${ROLE_OPTIONS.map(r => `<option value="${r}">${r}</option>`).join('')}
|
||
</select>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:1.5rem;">
|
||
<input type="checkbox" id="u-ativo" checked>
|
||
<label for="u-ativo" style="cursor:pointer;color:var(--text-primary);">Usuário Ativo</label>
|
||
</div>
|
||
<button type="submit" class="btn" style="width:100%;">Salvar</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
await loadUsers();
|
||
bindUserEvents();
|
||
}
|
||
|
||
async function loadUsers() {
|
||
const list = document.getElementById('users-list')!;
|
||
try {
|
||
const users = await api.get('/usuarios');
|
||
list.innerHTML = `
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr><th>Nome</th><th>E-mail</th><th>Cargo</th><th>Status</th><th>Desde</th><th>Ações</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
${users.map((u: any) => `
|
||
<tr>
|
||
<td data-label="Nome"><strong>${u.nome}</strong></td>
|
||
<td data-label="E-mail" style="color:var(--text-secondary)">${u.email}</td>
|
||
<td data-label="Cargo">${roleBadge(u.role)}</td>
|
||
<td data-label="Status"><span class="badge ${u.ativo ? 'badge-success' : 'badge-warning'}">${u.ativo ? 'Ativo' : 'Inativo'}</span></td>
|
||
<td data-label="Desde" style="color:var(--text-secondary)">${new Date(u.criadoEm).toLocaleDateString('pt-BR')}</td>
|
||
<td data-label="Ações" style="display:flex;gap:0.5rem;flex-wrap:wrap;">
|
||
<button class="btn btn-sm btn-secondary" onclick="window.editUser(${JSON.stringify(u).replace(/"/g, '"')})">✏️ Editar</button>
|
||
<button class="btn btn-sm btn-danger" onclick="window.toggleUser('${u.id}','${u.nome}',${u.ativo})">
|
||
${u.ativo ? '🚫 Desativar' : '✅ Ativar'}
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
} catch (err: any) {
|
||
list.innerHTML = `<p style="color:var(--accent-primary)">Erro: ${err.message}</p>`;
|
||
}
|
||
}
|
||
|
||
function bindUserEvents() {
|
||
document.getElementById('btn-novo-usuario')?.addEventListener('click', () => {
|
||
clearUserForm();
|
||
(document.getElementById('modal-usuario') as HTMLElement).style.display = 'flex';
|
||
(document.getElementById('u-senha') as HTMLInputElement).required = true;
|
||
(document.getElementById('senha-group') as HTMLElement).style.display = 'block';
|
||
});
|
||
|
||
document.getElementById('form-usuario')?.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
const id = (document.getElementById('user-id') as HTMLInputElement).value;
|
||
const senha = (document.getElementById('u-senha') as HTMLInputElement).value;
|
||
const payload: any = {
|
||
nome: (document.getElementById('u-nome') as HTMLInputElement).value,
|
||
email: (document.getElementById('u-email') as HTMLInputElement).value,
|
||
role: (document.getElementById('u-role') as HTMLSelectElement).value,
|
||
ativo: (document.getElementById('u-ativo') as HTMLInputElement).checked,
|
||
};
|
||
if (!id) payload.senha = senha;
|
||
|
||
try {
|
||
if (id) await api.patch(`/usuarios/${id}`, payload);
|
||
else await api.post('/usuarios', { ...payload, senha });
|
||
toast(id ? 'Usuário atualizado!' : 'Usuário cadastrado!', 'success');
|
||
(document.getElementById('modal-usuario') as HTMLElement).style.display = 'none';
|
||
await loadUsers();
|
||
} catch (err: any) { toast(err.message, 'error'); }
|
||
});
|
||
|
||
(window as any).editUser = (u: any) => {
|
||
(document.getElementById('user-id') as HTMLInputElement).value = u.id;
|
||
(document.getElementById('u-nome') as HTMLInputElement).value = u.nome;
|
||
(document.getElementById('u-email') as HTMLInputElement).value = u.email;
|
||
(document.getElementById('u-role') as HTMLSelectElement).value = u.role;
|
||
(document.getElementById('u-ativo') as HTMLInputElement).checked = u.ativo;
|
||
(document.getElementById('u-senha') as HTMLInputElement).required = false;
|
||
(document.getElementById('u-senha') as HTMLInputElement).value = '';
|
||
(document.getElementById('senha-group') as HTMLElement).style.display = 'none';
|
||
(document.getElementById('modal-user-title') as HTMLElement).textContent = 'Editar Usuário';
|
||
(document.getElementById('modal-usuario') as HTMLElement).style.display = 'flex';
|
||
};
|
||
|
||
(window as any).toggleUser = async (id: string, nome: string, ativo: boolean) => {
|
||
const action = ativo ? 'desativar' : 'ativar';
|
||
if (!confirm(`Deseja ${action} o usuário "${nome}"?`)) return;
|
||
try {
|
||
await api.patch(`/usuarios/${id}`, { ativo: !ativo });
|
||
toast(`Usuário ${ativo ? 'desativado' : 'ativado'}!`, 'success');
|
||
await loadUsers();
|
||
} catch (err: any) { toast(err.message, 'error'); }
|
||
};
|
||
}
|
||
|
||
function clearUserForm() {
|
||
(document.getElementById('user-id') as HTMLInputElement).value = '';
|
||
(document.getElementById('u-nome') as HTMLInputElement).value = '';
|
||
(document.getElementById('u-email') as HTMLInputElement).value = '';
|
||
(document.getElementById('u-senha') as HTMLInputElement).value = '';
|
||
(document.getElementById('u-role') as HTMLSelectElement).value = 'GARCOM';
|
||
(document.getElementById('u-ativo') as HTMLInputElement).checked = true;
|
||
(document.getElementById('modal-user-title') as HTMLElement).textContent = 'Novo Usuário';
|
||
}
|