diff --git a/backend/index.js b/backend/index.js index 99a2304..1c2d4dd 100644 --- a/backend/index.js +++ b/backend/index.js @@ -123,13 +123,56 @@ app.get('/api/attendances/:id', async (req, res) => { // --- Rotas de Tenants (Super Admin) --- app.get('/api/tenants', async (req, res) => { try { - const [rows] = await pool.query('SELECT * FROM tenants'); + // Buscar tenants e contar usuários/atendimentos + const query = ` + SELECT t.*, + (SELECT COUNT(*) FROM users u WHERE u.tenant_id = t.id) as user_count, + (SELECT COUNT(*) FROM attendances a WHERE a.tenant_id = t.id) as attendance_count + FROM tenants t + `; + const [rows] = await pool.query(query); res.json(rows); } catch (error) { res.status(500).json({ error: error.message }); } }); +// Criar Tenant +app.post('/api/tenants', async (req, res) => { + const { name, slug, admin_email, status } = req.body; + const crypto = require('crypto'); + + const connection = await pool.getConnection(); + try { + await connection.beginTransaction(); + + const tenantId = `tenant_${crypto.randomUUID().split('-')[0]}`; // Simple ID generation + + // 1. Criar Tenant + await connection.query( + 'INSERT INTO tenants (id, name, slug, admin_email, status) VALUES (?, ?, ?, ?, ?)', + [tenantId, name, slug, admin_email, status || 'active'] + ); + + // 2. Criar Usuário Admin Default + const userId = `u_${crypto.randomUUID().split('-')[0]}`; + await connection.query( + 'INSERT INTO users (id, tenant_id, name, email, role, status) VALUES (?, ?, ?, ?, ?, ?)', + [userId, tenantId, 'Admin', admin_email, 'admin', 'active'] + ); + + await connection.commit(); + res.status(201).json({ message: 'Tenant created successfully', id: tenantId }); + } catch (error) { + await connection.rollback(); + console.error('Erro ao criar tenant:', error); + res.status(500).json({ error: error.message }); + } finally { + connection.release(); + } +}); + + // Serve index.html for any unknown routes (for client-side routing) if (process.env.NODE_ENV === 'production') { app.get('*', (req, res) => { diff --git a/pages/SuperAdmin.tsx b/pages/SuperAdmin.tsx index 473bc88..cde5147 100644 --- a/pages/SuperAdmin.tsx +++ b/pages/SuperAdmin.tsx @@ -3,7 +3,7 @@ import { Building2, Users, MessageSquare, Plus, Search, MoreHorizontal, Edit, Trash2, Shield, Calendar, ChevronDown, ChevronUp, ChevronsUpDown, X } from 'lucide-react'; -import { TENANTS, MOCK_ATTENDANCES, USERS } from '../constants'; +import { getTenants, createTenant } from '../services/dataService'; import { Tenant } from '../types'; import { DateRangePicker } from '../components/DateRangePicker'; import { KPICard } from '../components/KPICard'; @@ -18,7 +18,8 @@ export const SuperAdmin: React.FC = () => { }); const [selectedTenantId, setSelectedTenantId] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); - const [tenants, setTenants] = useState(TENANTS); + const [tenants, setTenants] = useState([]); + const [loading, setLoading] = useState(true); const [isModalOpen, setIsModalOpen] = useState(false); const [editingTenant, setEditingTenant] = useState(null); @@ -26,6 +27,18 @@ export const SuperAdmin: React.FC = () => { const [sortKey, setSortKey] = useState('created_at'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + // Load Tenants from API + const loadTenants = async () => { + setLoading(true); + const data = await getTenants(); + setTenants(data); + setLoading(false); + }; + + React.useEffect(() => { + loadTenants(); + }, []); + // --- Metrics --- const totalTenants = tenants.length; // Mock aggregation for demo @@ -82,16 +95,31 @@ export const SuperAdmin: React.FC = () => { const handleDelete = (id: string) => { if (confirm('Tem certeza que deseja excluir esta organização? Esta ação não pode ser desfeita.')) { + // TODO: Implement delete API setTenants(prev => prev.filter(t => t.id !== id)); } }; - const handleSaveTenant = (e: React.FormEvent) => { + const handleSaveTenant = async (e: React.FormEvent) => { e.preventDefault(); - // Logic to save (mock) - setIsModalOpen(false); - setEditingTenant(null); - alert('Organização salva com sucesso (Mock)'); + const form = e.target as HTMLFormElement; + + // Extract values manually or use state. For simplicity using form elements + const name = (form.elements.namedItem('name') as HTMLInputElement).value; + const slug = (form.elements.namedItem('slug') as HTMLInputElement).value; + const admin_email = (form.elements.namedItem('admin_email') as HTMLInputElement).value; + const status = (form.elements.namedItem('status') as HTMLSelectElement).value; + + const success = await createTenant({ name, slug, admin_email, status }); + + if (success) { + setIsModalOpen(false); + setEditingTenant(null); + loadTenants(); // Reload list + alert('Organização salva com sucesso!'); + } else { + alert('Erro ao salvar organização. Verifique o console.'); + } }; // --- Helper Components --- @@ -297,6 +325,7 @@ export const SuperAdmin: React.FC = () => { { {
=> { + try { + const response = await fetch(`${API_URL}/tenants`); + if (!response.ok) throw new Error('Falha ao buscar tenants'); + return await response.json(); + } catch (error) { + console.error("API Error (getTenants):", error); + return []; + } +}; + +export const createTenant = async (tenantData: any): Promise => { + try { + const response = await fetch(`${API_URL}/tenants`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(tenantData) + }); + return response.ok; + } catch (error) { + console.error("API Error (createTenant):", error); + return false; + } +};