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:
farelos
2026-02-23 10:36:00 -03:00
parent 0cf4fb92d7
commit 3250ad7537
26 changed files with 2947 additions and 8 deletions

130
backend/index.js Normal file
View 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}`);
});