Revert "feat: implement real tenant creation and listing"
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m17s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m17s
This reverts commit 2742bafb00.
This commit is contained in:
@@ -123,56 +123,13 @@ app.get('/api/attendances/:id', async (req, res) => {
|
|||||||
// --- Rotas de Tenants (Super Admin) ---
|
// --- Rotas de Tenants (Super Admin) ---
|
||||||
app.get('/api/tenants', async (req, res) => {
|
app.get('/api/tenants', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Buscar tenants e contar usuários/atendimentos
|
const [rows] = await pool.query('SELECT * FROM tenants');
|
||||||
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);
|
res.json(rows);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
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)
|
// Serve index.html for any unknown routes (for client-side routing)
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
Building2, Users, MessageSquare, Plus, Search, MoreHorizontal,
|
Building2, Users, MessageSquare, Plus, Search, MoreHorizontal,
|
||||||
Edit, Trash2, Shield, Calendar, ChevronDown, ChevronUp, ChevronsUpDown, X
|
Edit, Trash2, Shield, Calendar, ChevronDown, ChevronUp, ChevronsUpDown, X
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { getTenants, createTenant } from '../services/dataService';
|
import { TENANTS, MOCK_ATTENDANCES, USERS } from '../constants';
|
||||||
import { Tenant } from '../types';
|
import { Tenant } from '../types';
|
||||||
import { DateRangePicker } from '../components/DateRangePicker';
|
import { DateRangePicker } from '../components/DateRangePicker';
|
||||||
import { KPICard } from '../components/KPICard';
|
import { KPICard } from '../components/KPICard';
|
||||||
@@ -18,8 +18,7 @@ export const SuperAdmin: React.FC = () => {
|
|||||||
});
|
});
|
||||||
const [selectedTenantId, setSelectedTenantId] = useState<string>('all');
|
const [selectedTenantId, setSelectedTenantId] = useState<string>('all');
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [tenants, setTenants] = useState<Tenant[]>([]);
|
const [tenants, setTenants] = useState<Tenant[]>(TENANTS);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [editingTenant, setEditingTenant] = useState<Tenant | null>(null);
|
const [editingTenant, setEditingTenant] = useState<Tenant | null>(null);
|
||||||
|
|
||||||
@@ -27,18 +26,6 @@ export const SuperAdmin: React.FC = () => {
|
|||||||
const [sortKey, setSortKey] = useState<keyof Tenant>('created_at');
|
const [sortKey, setSortKey] = useState<keyof Tenant>('created_at');
|
||||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
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 ---
|
// --- Metrics ---
|
||||||
const totalTenants = tenants.length;
|
const totalTenants = tenants.length;
|
||||||
// Mock aggregation for demo
|
// Mock aggregation for demo
|
||||||
@@ -95,31 +82,16 @@ export const SuperAdmin: React.FC = () => {
|
|||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
if (confirm('Tem certeza que deseja excluir esta organização? Esta ação não pode ser desfeita.')) {
|
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));
|
setTenants(prev => prev.filter(t => t.id !== id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveTenant = async (e: React.FormEvent) => {
|
const handleSaveTenant = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const form = e.target as HTMLFormElement;
|
// Logic to save (mock)
|
||||||
|
setIsModalOpen(false);
|
||||||
// Extract values manually or use state. For simplicity using form elements
|
setEditingTenant(null);
|
||||||
const name = (form.elements.namedItem('name') as HTMLInputElement).value;
|
alert('Organização salva com sucesso (Mock)');
|
||||||
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 ---
|
// --- Helper Components ---
|
||||||
@@ -325,7 +297,6 @@ export const SuperAdmin: React.FC = () => {
|
|||||||
<label className="text-sm font-medium text-slate-700">Nome da Organização</label>
|
<label className="text-sm font-medium text-slate-700">Nome da Organização</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
|
||||||
defaultValue={editingTenant?.name}
|
defaultValue={editingTenant?.name}
|
||||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
||||||
placeholder="ex. Acme Corp"
|
placeholder="ex. Acme Corp"
|
||||||
@@ -338,7 +309,6 @@ export const SuperAdmin: React.FC = () => {
|
|||||||
<label className="text-sm font-medium text-slate-700">Slug</label>
|
<label className="text-sm font-medium text-slate-700">Slug</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="slug"
|
|
||||||
defaultValue={editingTenant?.slug}
|
defaultValue={editingTenant?.slug}
|
||||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
||||||
placeholder="ex. acme-corp"
|
placeholder="ex. acme-corp"
|
||||||
@@ -347,7 +317,6 @@ export const SuperAdmin: React.FC = () => {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-slate-700">Status</label>
|
<label className="text-sm font-medium text-slate-700">Status</label>
|
||||||
<select
|
<select
|
||||||
name="status"
|
|
||||||
defaultValue={editingTenant?.status || 'active'}
|
defaultValue={editingTenant?.status || 'active'}
|
||||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
||||||
>
|
>
|
||||||
@@ -362,7 +331,6 @@ export const SuperAdmin: React.FC = () => {
|
|||||||
<label className="text-sm font-medium text-slate-700">E-mail do Admin</label>
|
<label className="text-sm font-medium text-slate-700">E-mail do Admin</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="admin_email"
|
|
||||||
defaultValue={editingTenant?.admin_email}
|
defaultValue={editingTenant?.admin_email}
|
||||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
className="w-full px-3 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-blue-100 outline-none"
|
||||||
placeholder="admin@empresa.com"
|
placeholder="admin@empresa.com"
|
||||||
|
|||||||
@@ -67,28 +67,3 @@ export const getAttendanceById = async (id: string): Promise<Attendance | undefi
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTenants = async (): Promise<any[]> => {
|
|
||||||
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<boolean> => {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user