From 1330147eafa786a2cd2ff1563d9fc4493c607d45 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Wed, 13 May 2026 21:43:23 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20corrige=20inconsist=C3=AAncias=20cr?= =?UTF-8?q?=C3=ADticas=20de=20backend=20e=20navega=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Backend: substitui string literals com aspas duplas por aspas simples nas queries PostgreSQL (settings, GTO builder, google_tokens delete) - Backend: corrige alias SQL 'dentistanome' para 'dentistaNome' (quoted) e ajusta frontend para usar 'pacientenome' (lowercase do PG) - Backend: move app.listen() para após todas as rotas registradas - Backend: ativa generateIntelligentNotifications no startup + intervalo 5min - Backend: magic link usa process.env.APP_URL em vez de localhost:3000 hardcoded - Frontend: adiciona 'relatorios' às permissões do role 'funcionario' - Frontend: adiciona onClick ao botão Relatórios do Dashboard - Frontend: adiciona Content-Type e JWT token em salvarAgendamento/atualizarAgendamento - Frontend: MeusTratamentos passa JWT token nas chamadas a /api/guias - Frontend: ClinicasView passa JWT token no endpoint de atualização de cor - Frontend: corrige subtítulo 'MYSQL + GOOGLE' para 'POSTGRESQL + GOOGLE' Co-Authored-By: Claude Sonnet 4.6 --- backend/server.js | 33 +++++++++++++++++++----------- frontend/App.tsx | 2 +- frontend/components/Sidebar.tsx | 2 +- frontend/services/backend.ts | 2 ++ frontend/views/ClinicasView.tsx | 6 +++++- frontend/views/Dashboard.tsx | 13 ++++++++---- frontend/views/LoginView.tsx | 2 +- frontend/views/MeusTratamentos.tsx | 5 ++++- 8 files changed, 44 insertions(+), 21 deletions(-) diff --git a/backend/server.js b/backend/server.js index 147e672..8c123fc 100644 --- a/backend/server.js +++ b/backend/server.js @@ -262,8 +262,6 @@ const createOAuth2Client = () => new google.auth.OAuth2( process.env.GOOGLE_REDIRECT_URI || `http://localhost:${PORT}/api/oauth2callback` ); -app.listen(PORT, '0.0.0.0', () => console.log(`[SERVER] Running on http://0.0.0.0:${PORT}`)); - // --- GOOGLE AUTH ROUTES --- app.get('/api/auth/google/url', (req, res) => { const { dentistId } = req.query; @@ -321,7 +319,7 @@ app.get('/api/auth/google/status', async (req, res) => { app.delete('/api/auth/google/:ownerId', async (req, res) => { try { - await pool.query('DELETE FROM google_tokens WHERE owner_id = ?', [req.params.ownerId]); + await pool.query('DELETE FROM google_tokens WHERE owner_id = $1', [req.params.ownerId]); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); @@ -329,7 +327,7 @@ app.delete('/api/auth/google/:ownerId', async (req, res) => { // --- SETTINGS --- app.get('/api/settings', async (req, res) => { try { - const [rows] = await pool.query('SELECT * FROM settings WHERE id = "main"'); + const { rows } = await pool.query("SELECT * FROM settings WHERE id = 'main'"); res.json(rows[0] ? JSON.parse(rows[0].data) : {}); } catch (err) { res.status(500).json({ error: err.message }); } }); @@ -337,7 +335,11 @@ app.get('/api/settings', async (req, res) => { app.post('/api/settings', async (req, res) => { try { const data = JSON.stringify(req.body); - await pool.query('INSERT INTO settings (id, category, data) VALUES ("main", "general", ?) ON DUPLICATE KEY UPDATE data = ?', [data, data]); + await pool.query( + `INSERT INTO settings (id, category, data) VALUES ('main', 'general', $1) + ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data`, + [data] + ); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); @@ -347,8 +349,8 @@ app.post('/api/dentistas/magic-link', async (req, res) => { const { clinicaId, dentistName } = req.body; try { const token = Math.random().toString(36).substring(2, 15); - // Em um sistema real, salvaríamos esse token em uma tabela de convites - const magicLink = `http://localhost:3000/#/cadastro-dentista?token=${token}&clinica=${clinicaId}&nome=${encodeURIComponent(dentistName)}`; + const appUrl = process.env.APP_URL || `http://localhost:${process.env.FRONTEND_PORT || 3000}`; + const magicLink = `${appUrl}/#/cadastro-dentista?token=${token}&clinica=${clinicaId}&nome=${encodeURIComponent(dentistName)}`; res.json({ success: true, link: magicLink }); } catch (err) { res.status(500).json({ error: err.message }); } }); @@ -437,7 +439,7 @@ app.get('/api/google/events', async (req, res) => { } // 2. Legacy Support: Check Settings for Public Calendars (API KEY) - const [rows] = await pool.query('SELECT data FROM settings WHERE id = "main"'); + const [rows] = await pool.query("SELECT data FROM settings WHERE id = 'main'"); if (rows[0]) { const settings = JSON.parse(rows[0].data); const apiKey = settings.googleApiKey; @@ -513,7 +515,8 @@ app.get('/api/dashboard/stats', async (req, res) => { // Recent appointments with patient and dentist names const { rows: recent } = await pool.query(` - SELECT a.*, d.nome as dentistanome + SELECT a.id, a.pacientenome, a.start_time, a.status, + d.nome as "dentistaNome" FROM agendamentos a LEFT JOIN dentistas d ON a.dentistaid = d.id ORDER BY a.start_time DESC @@ -1159,7 +1162,7 @@ app.post('/api/gto-builder', async (req, res) => { const { pacienteId, dentistaId, credenciadaId } = req.body; const id = Math.random().toString(36).substring(2, 9).toUpperCase(); await pool.query( - 'INSERT INTO gto_builders (id, pacienteId, dentistaId, credenciadaId, status, total) VALUES (?, ?, ?, ?, "RASCUNHO", 0)', + "INSERT INTO gto_builders (id, pacienteId, dentistaId, credenciadaId, status, total) VALUES ($1, $2, $3, $4, 'RASCUNHO', 0)", [id, pacienteId, dentistaId, credenciadaId] ); res.json({ id }); @@ -1204,7 +1207,7 @@ app.delete('/api/gto-builder/:id/item/:itemId', async (req, res) => { app.put('/api/gto-builder/:id/finalizar', async (req, res) => { try { - await pool.query('UPDATE gto_builders SET status = "FINALIZADA" WHERE id = ?', [req.params.id]); + await pool.query("UPDATE gto_builders SET status = 'FINALIZADA' WHERE id = $1", [req.params.id]); // Logic to migrate to guias_odontologicas would go here in a real app // For now, we just mark as finished. @@ -1297,5 +1300,11 @@ async function generateIntelligentNotifications() { } } - +// Start server after all routes are registered +app.listen(PORT, '0.0.0.0', () => { + console.log(`[SERVER] Running on http://0.0.0.0:${PORT}`); + // Run immediately on startup, then every 5 minutes + generateIntelligentNotifications(); + setInterval(generateIntelligentNotifications, 5 * 60 * 1000); +}); diff --git a/frontend/App.tsx b/frontend/App.tsx index 803dc08..633227b 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -136,7 +136,7 @@ const App: React.FC = () => { const permissions: Record = { paciente: ['tratamentos', 'notificacoes'], dentista: ['dashboard', 'pacientes', 'agenda', 'ortodontia', 'tratamentos', 'notificacoes', 'clinicas'], - funcionario: ['dashboard', 'leads', 'pacientes', 'agenda', 'financeiro', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas'], + funcionario: ['dashboard', 'leads', 'pacientes', 'agenda', 'financeiro', 'tratamentos', 'lancar-gto', 'relatorios', 'notificacoes', 'clinicas'], }; return permissions[role]?.includes(view) || false; diff --git a/frontend/components/Sidebar.tsx b/frontend/components/Sidebar.tsx index 80327fc..a4a2487 100644 --- a/frontend/components/Sidebar.tsx +++ b/frontend/components/Sidebar.tsx @@ -76,7 +76,7 @@ export const Sidebar: React.FC = ({ activeTab, setActiveTab }) =>
SCOREODONTO - MYSQL + GOOGLE + POSTGRESQL + GOOGLE
diff --git a/frontend/services/backend.ts b/frontend/services/backend.ts index 3f301ab..f060e7a 100644 --- a/frontend/services/backend.ts +++ b/frontend/services/backend.ts @@ -449,6 +449,7 @@ export const HybridBackend = { salvarAgendamento: async (ag: Partial) => { const res = await apiFetch(`${API_URL}/agendamentos`, { method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(ag) }); if (!res.ok) { @@ -461,6 +462,7 @@ export const HybridBackend = { atualizarAgendamento: async (id: string, data: Partial) => { const res = await apiFetch(`${API_URL}/agendamentos/${id}`, { method: 'PUT', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!res.ok) { diff --git a/frontend/views/ClinicasView.tsx b/frontend/views/ClinicasView.tsx index 92378e1..87beec3 100644 --- a/frontend/views/ClinicasView.tsx +++ b/frontend/views/ClinicasView.tsx @@ -38,9 +38,13 @@ export const ClinicasView: React.FC = () => { const handleUpdateColor = async (workspaceId: string, color: string) => { setSavingColor(true); try { + const token = localStorage.getItem('SCOREODONTO_AUTH_TOKEN'); const res = await fetch(`/api/clinicas/${workspaceId}/color`, { method: 'PUT', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + }, body: JSON.stringify({ cor: color }) }); diff --git a/frontend/views/Dashboard.tsx b/frontend/views/Dashboard.tsx index f67ffba..516e089 100644 --- a/frontend/views/Dashboard.tsx +++ b/frontend/views/Dashboard.tsx @@ -126,9 +126,14 @@ export const Dashboard: React.FC = () => { />
- + {(currentRole === 'admin' || currentRole === 'donoclinica' || currentRole === 'funcionario') && ( + + )} {(currentRole === 'admin' || currentRole === 'donoclinica' || currentRole === 'funcionario') && (
-
{app.pacienteNome || 'Paciente'}
+
{app.pacientenome || 'Paciente'}
{app.dentistaNome || 'Dr. Dentista'} • {new Date(app.start_time).toLocaleDateString('pt-BR')} {new Date(app.start_time).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}
diff --git a/frontend/views/LoginView.tsx b/frontend/views/LoginView.tsx index ed01c05..ddc9820 100644 --- a/frontend/views/LoginView.tsx +++ b/frontend/views/LoginView.tsx @@ -55,7 +55,7 @@ export const LoginView: React.FC = ({ onLoginSuccess }) => {

SCOREODONTO

-

SISTEMA INTEGRADO MYSQL + GOOGLE

+

SISTEMA INTEGRADO POSTGRESQL + GOOGLE

diff --git a/frontend/views/MeusTratamentos.tsx b/frontend/views/MeusTratamentos.tsx index a34db36..1520581 100644 --- a/frontend/views/MeusTratamentos.tsx +++ b/frontend/views/MeusTratamentos.tsx @@ -91,7 +91,10 @@ export const MeusTratamentos: React.FC = () => { sortOrder }); - const res = await fetch(`/api/guias?${params}`); + const token = localStorage.getItem('SCOREODONTO_AUTH_TOKEN'); + const res = await fetch(`/api/guias?${params}`, { + headers: token ? { 'Authorization': `Bearer ${token}` } : {} + }); if (!res.ok) throw new Error('Falha ao carregar guias'); return res.json(); };