feat: Implement backend API and basic frontend structure
Adds initial backend API endpoints for fetching users and attendances, including basic filtering. Sets up the frontend routing with a layout component and includes placeholder pages for dashboard, users, and login. Refactors the README for local development setup.
This commit is contained in:
28
backend/db.js
Normal file
28
backend/db.js
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
// Configuração da conexão com o banco de dados
|
||||
// Em produção, estes valores devem vir de variáveis de ambiente (.env)
|
||||
const pool = mysql.createPool({
|
||||
host: '162.240.103.190',
|
||||
user: 'agenciac_comia',
|
||||
password: 'Blyzer@2025#',
|
||||
database: 'agenciac_comia',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
// Opções de SSL podem ser necessárias dependendo do servidor,
|
||||
// mas vamos tentar sem SSL inicialmente dado o host.
|
||||
});
|
||||
|
||||
// Teste de conexão simples ao iniciar
|
||||
pool.getConnection()
|
||||
.then(connection => {
|
||||
console.log('✅ Conectado ao MySQL com sucesso!');
|
||||
connection.release();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('❌ Erro ao conectar ao MySQL:', err.message);
|
||||
});
|
||||
|
||||
module.exports = pool;
|
||||
130
backend/index.js
Normal file
130
backend/index.js
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const pool = require('./db');
|
||||
|
||||
const app = express();
|
||||
const PORT = 3001; // Porta do backend
|
||||
|
||||
app.use(cors()); // Permite que o React (localhost:3000) acesse este servidor
|
||||
app.use(express.json());
|
||||
|
||||
// --- Rotas de Usuários ---
|
||||
|
||||
// Listar Usuários (com filtro opcional de tenant)
|
||||
app.get('/api/users', async (req, res) => {
|
||||
try {
|
||||
const { tenantId } = req.query;
|
||||
let query = 'SELECT * FROM users';
|
||||
const params = [];
|
||||
|
||||
if (tenantId && tenantId !== 'all') {
|
||||
query += ' WHERE tenant_id = ?';
|
||||
params.push(tenantId);
|
||||
}
|
||||
|
||||
const [rows] = await pool.query(query, params);
|
||||
res.json(rows);
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar usuários:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Detalhe do Usuário
|
||||
app.get('/api/users/:id', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
|
||||
if (rows.length === 0) return res.status(404).json({ message: 'User not found' });
|
||||
res.json(rows[0]);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Rotas de Atendimentos ---
|
||||
|
||||
// Listar Atendimentos (Dashboard)
|
||||
app.get('/api/attendances', async (req, res) => {
|
||||
try {
|
||||
const { tenantId, userId, teamId, startDate, endDate } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT a.*, u.team_id
|
||||
FROM attendances a
|
||||
JOIN users u ON a.user_id = u.id
|
||||
WHERE a.tenant_id = ?
|
||||
`;
|
||||
const params = [tenantId];
|
||||
|
||||
// Filtro de Data
|
||||
if (startDate && endDate) {
|
||||
query += ' AND a.created_at BETWEEN ? AND ?';
|
||||
params.push(new Date(startDate), new Date(endDate));
|
||||
}
|
||||
|
||||
// Filtro de Usuário
|
||||
if (userId && userId !== 'all') {
|
||||
query += ' AND a.user_id = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
|
||||
// Filtro de Time (baseado na tabela users ou teams)
|
||||
if (teamId && teamId !== 'all') {
|
||||
query += ' AND u.team_id = ?';
|
||||
params.push(teamId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY a.created_at DESC';
|
||||
|
||||
const [rows] = await pool.query(query, params);
|
||||
|
||||
// Tratamento de campos JSON se o MySQL retornar como string
|
||||
const processedRows = rows.map(row => ({
|
||||
...row,
|
||||
attention_points: typeof row.attention_points === 'string' ? JSON.parse(row.attention_points) : row.attention_points,
|
||||
improvement_points: typeof row.improvement_points === 'string' ? JSON.parse(row.improvement_points) : row.improvement_points,
|
||||
converted: Boolean(row.converted) // Garantir booleano
|
||||
}));
|
||||
|
||||
res.json(processedRows);
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar atendimentos:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Detalhe do Atendimento
|
||||
app.get('/api/attendances/:id', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM attendances WHERE id = ?', [req.params.id]);
|
||||
if (rows.length === 0) return res.status(404).json({ message: 'Attendance not found' });
|
||||
|
||||
const row = rows[0];
|
||||
const processedRow = {
|
||||
...row,
|
||||
attention_points: typeof row.attention_points === 'string' ? JSON.parse(row.attention_points) : row.attention_points,
|
||||
improvement_points: typeof row.improvement_points === 'string' ? JSON.parse(row.improvement_points) : row.improvement_points,
|
||||
converted: Boolean(row.converted)
|
||||
};
|
||||
|
||||
res.json(processedRow);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Rotas de Tenants (Super Admin) ---
|
||||
app.get('/api/tenants', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM tenants');
|
||||
res.json(rows);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Servidor Backend rodando em http://localhost:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user