🔧

Sistema de Assistência Técnica

Não tem conta? Criar conta

`; const printWin = window.open('', '_blank'); if (!printWin) { showToast('Permita popups para imprimir a OS', 'error'); return; } printWin.document.write(printHtml); printWin.document.close(); // Auto-trigger print dialog after load printWin.onload = function() { setTimeout(function() { printWin.print(); }, 300); }; } /* ============ CLIENTS ============ */ async function renderClients() { const mc = document.getElementById('mainContent'); mc.innerHTML = `

Clientes

👥 Clientes
NomeTelefoneEmailCPFAções
Carregando...
`; loadClients(); } let loadClientsTimer; function loadClientsDebounced() { clearTimeout(loadClientsTimer); loadClientsTimer = setTimeout(loadClients, 350); } async function loadClients() { const body = document.getElementById('clientsBody'); if (!body) return; const q = document.getElementById('clientSearch') ? document.getElementById('clientSearch').value : ''; try { let url = '/api/clientes?limit=100'; if (q) url += '&search=' + encodeURIComponent(q); const data = await api(url); const clients = Array.isArray(data) ? data : (data.items || []); if (clients.length === 0) { body.innerHTML = 'Nenhum cliente encontrado'; return; } body.innerHTML = clients.map(c => ` ${esc(c.nome || c.name)} ${esc(c.telefone || c.phone || '—')} ${esc(c.email || '—')} ${esc(c.cpf || '—')} `).join(''); } catch (err) { body.innerHTML = `Erro: ${esc(err.message)}`; } } function showClientModal(id) { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); if (id) { api('/api/clientes/' + id).then(c => { document.getElementById('cName').value = c.nome || c.name || ''; document.getElementById('cPhone').value = c.telefone || c.phone || ''; document.getElementById('cEmail').value = c.email || ''; document.getElementById('cCpf').value = c.cpf || ''; }).catch(err => showToast(err.message, 'error')); } document.getElementById('clientForm').addEventListener('submit', async (e) => { e.preventDefault(); const payload = { name: document.getElementById('cName').value.trim(), phone: document.getElementById('cPhone').value.trim(), email: document.getElementById('cEmail').value.trim(), cpf: document.getElementById('cCpf').value.trim() }; try { if (id) { await api('/api/clientes/' + id, { method: 'PUT', body: JSON.stringify(payload) }); showToast('Cliente atualizado!', 'success'); } else { await api('/api/clientes', { method: 'POST', body: JSON.stringify(payload) }); showToast('Cliente cadastrado!', 'success'); } overlay.remove(); loadClients(); } catch (err) { showToast(err.message, 'error'); } }); } /* ============ TECHNICIANS ============ */ async function renderTechs() { const mc = document.getElementById('mainContent'); mc.innerHTML = `

Técnicos

🛠️ Técnicos
NomeEspecialidadeTelefoneEmailAções
Carregando...
`; loadTechs(); } async function loadTechs() { const body = document.getElementById('techsBody'); if (!body) return; try { const data = await api('/api/tecnicos?limit=100'); const techs = Array.isArray(data) ? data : (data.items || []); if (techs.length === 0) { body.innerHTML = 'Nenhum técnico cadastrado'; return; } body.innerHTML = techs.map(t => ` ${esc(t.name)} ${esc(t.specialty || t.specialty || '—')} ${esc(t.telefone || t.phone || '—')} ${esc(t.email || '—')} `).join(''); } catch (err) { body.innerHTML = `Erro: ${esc(err.message)}`; } } function showTechModal(id) { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); if (id) { api('/api/tecnicos/' + id).then(t => { document.getElementById('tName').value = t.name || ''; document.getElementById('tSpecialty').value = t.specialty || ''; document.getElementById('tPhone').value = t.telefone || t.phone || ''; document.getElementById('tEmail').value = t.email || ''; }).catch(err => showToast(err.message, 'error')); } document.getElementById('techForm').addEventListener('submit', async (e) => { e.preventDefault(); const payload = { nome: document.getElementById('tName').value.trim(), telefone: document.getElementById('tPhone').value.trim() }; try { if (id) { await api('/api/tecnicos/' + id, { method: 'PUT', body: JSON.stringify(payload) }); showToast('Técnico atualizado!', 'success'); } else { await api('/api/tecnicos', { method: 'POST', body: JSON.stringify(payload) }); showToast('Técnico cadastrado!', 'success'); } overlay.remove(); loadTechs(); } catch (err) { showToast(err.message, 'error'); } }); } /* ============ REPORTS ============ */ async function renderReports() { const today = new Date().toISOString().split('T')[0]; const firstDay = new Date(); firstDay.setDate(1); const firstDayStr = firstDay.toISOString().split('T')[0]; const mc = document.getElementById('mainContent'); mc.innerHTML = `

Relatórios

📈 Relatórios

Faturamento Total

Total de OS

OS por Técnico

TécnicoOSFaturamento
Gere o relatório

OS por Categoria

CategoriaOSFaturamento
Gere o relatório
`; loadReports(); } async function loadReports() { const start = document.getElementById('repDateStart').value; const end = document.getElementById('repDateEnd').value; if (!start || !end) return; // Revenue + total try { const data = await api('/api/dashboard?start=' + start + '&end=' + end); document.getElementById('repRevenue').textContent = formatCurrency(data.total || data.month || 0); document.getElementById('repTotal').textContent = data.count || data.order_count || 0; } catch (err) { document.getElementById('repRevenue').textContent = formatCurrency(0); document.getElementById('repTotal').textContent = '0'; } // By technician try { const techData = await api('/api/relatorios?start=' + start + '&end=' + end); const techs = Array.isArray(techData) ? techData : (techData.items || []); if (techs.length === 0) { document.getElementById('repByTech').innerHTML = 'Sem dados'; } else { document.getElementById('repByTech').innerHTML = techs.map(t => ` ${esc(t.name || t.technician_name || '—')} ${t.count || t.order_count || 0} ${formatCurrency(t.revenue || t.total || 0)} `).join(''); } } catch (err) { document.getElementById('repByTech').innerHTML = 'Sem dados'; } // By category try { const catData = await api('/api/relatorios?start=' + start + '&end=' + end); const cats = Array.isArray(catData) ? catData : (catData.items || []); if (cats.length === 0) { document.getElementById('repByCat').innerHTML = 'Sem dados'; } else { document.getElementById('repByCat').innerHTML = cats.map(c => ` ${esc(c.category || c.defect_category || '—')} ${c.count || c.order_count || 0} ${formatCurrency(c.revenue || c.total || 0)} `).join(''); } } catch (err) { document.getElementById('repByCat').innerHTML = 'Sem dados'; } } /* ============ PLANO / PAYMENTS ============ */ async function renderPlano() { const mc = document.getElementById('mainContent'); mc.innerHTML = `

Plano

💎 Meu Plano
Carregando...
`; try { const data = await api('/api/payments/status'); const plan = data.plan || {}; const planName = plan.plan || 'free'; const daysLeft = plan.days_left || 0; const recentPayments = data.recent_payments || []; let statusHtml = ''; if (planName === 'trial') { statusHtml = `
🚀 Período de Teste

Você tem ${daysLeft} dias restantes do trial gratuito.

`; } else if (planName === 'pro') { statusHtml = `
💎 Plano Pro

OS ilimitada — obrigado pela preferência!

`; } else { statusHtml = `
📋 Plano Free

Limite de 5 OS por mês. Faça upgrade para OS ilimitada!

🔒
`; } const planCards = `
Mensal
R$ 19,00/mês
ECONOMIZE 56%
Anual
R$ 99,00/ano
`; const couponHtml = `

🎟️ Cupom de Desconto

Cupons disponíveis: BEMVINDO10, BEMVINDO20, CELLOS50, CELLOS100

`; const paymentsHtml = recentPayments.length > 0 ? `

📊 Histórico de Pagamentos

${recentPayments.map(p => ` `).join('')}
DataPlanoValorStatus
${formatDateShort(p.created_at)} ${esc(p.plan)} R$ ${(p.amount_cents / 100).toFixed(2).replace('.', ',')} ${esc(p.status)}
` : ''; document.getElementById('planoContent').innerHTML = statusHtml + planCards + couponHtml + paymentsHtml; } catch (err) { document.getElementById('planoContent').innerHTML = `
Erro: ${esc(err.message)}
`; } } let currentCoupon = null; let currentDiscount = 0; async function applyCoupon() { const code = document.getElementById('couponInput').value.trim().toUpperCase(); if (!code) return; try { const result = await api('/api/payments/apply-coupon', { method: 'POST', body: JSON.stringify({ code: code, plan: 'monthly' }) }); currentCoupon = code; currentDiscount = result.discount_percent; document.getElementById('couponResult').innerHTML = `
✅ Cupom ${esc(result.code)} aplicado: ${result.discount_percent}% de desconto!
Preço original: R$ ${(result.original_price_cents / 100).toFixed(2).replace('.', ',')} → Preço final: ${esc(result.final_price_brl)}
`; showToast(`Cupom ${code} aplicado: ${result.discount_percent}% de desconto!`, 'success'); } catch (err) { currentCoupon = null; currentDiscount = 0; document.getElementById('couponResult').innerHTML = `
❌ ${esc(err.message)}
`; showToast(err.message || 'Cupom inválido', 'error'); } } async function startPayment(plan) { if (!confirm(`Confirmar assinatura do plano ${plan === 'yearly' ? 'anual (R$ 99/ano)' : 'mensal (R$ 19/mês)'}?`)) return; try { const body = { plan }; if (currentCoupon) body.coupon_code = currentCoupon; const data = await api('/api/payments/create', { method: 'POST', body: JSON.stringify(body) }); if (data.checkout_url) { showToast('Redirecionando para o pagamento...', 'success'); window.open(data.checkout_url, '_blank'); } else { showToast('Erro ao gerar link de pagamento', 'error'); } } catch (err) { showToast(err.message || 'Erro ao iniciar pagamento', 'error'); } } /* ============ INIT ============ */ if (checkAuth()) { navigate('dashboard'); }