- Refatora CourseManagement para listar dados de /api/cadastro-fot - Cria componente FotForm para novo cadastro de turmas - Adiciona validação de unicidade para número FOT - Integra dropdowns com endpoints /api/cursos e /api/anos-formaturas - Corrige duplicidade no registro de profissionais no backend
276 lines
11 KiB
TypeScript
276 lines
11 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import { useAuth } from "../contexts/AuthContext";
|
|
import { UserRole } from "../types";
|
|
import { Button } from "../components/Button";
|
|
import { getCadastroFot } from "../services/apiService";
|
|
import { Briefcase, AlertTriangle, Plus, Edit } from "lucide-react";
|
|
import { FotForm } from "../components/FotForm";
|
|
|
|
interface FotData {
|
|
id: string;
|
|
fot: number;
|
|
empresa_nome: string;
|
|
curso_nome: string;
|
|
observacoes: string;
|
|
instituicao: string;
|
|
ano_formatura_label: string;
|
|
cidade: string;
|
|
estado: string;
|
|
gastos_captacao: number;
|
|
pre_venda: boolean;
|
|
}
|
|
|
|
export const CourseManagement: React.FC = () => {
|
|
const { user } = useAuth();
|
|
const [fotList, setFotList] = useState<FotData[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [showForm, setShowForm] = useState(false);
|
|
|
|
// Verificar se è admin
|
|
const isAdmin =
|
|
user?.role === UserRole.SUPERADMIN ||
|
|
user?.role === UserRole.BUSINESS_OWNER;
|
|
|
|
useEffect(() => {
|
|
fetchFotData();
|
|
}, [isAdmin]);
|
|
|
|
const fetchFotData = async () => {
|
|
if (!isAdmin) return;
|
|
|
|
const token = localStorage.getItem("token");
|
|
if (!token) return;
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
const response = await getCadastroFot(token);
|
|
if (response.data) {
|
|
setFotList(response.data);
|
|
setError(null);
|
|
} else if (response.error) {
|
|
setError(response.error);
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
setError("Erro ao carregar dados.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleFormSubmit = () => {
|
|
setShowForm(false);
|
|
fetchFotData(); // Refresh list after successful creation
|
|
};
|
|
|
|
// Extract existing FOT numbers for uniqueness validation
|
|
const existingFots = fotList.map(item => item.fot);
|
|
|
|
if (!isAdmin) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 pt-32 pb-12">
|
|
<div className="max-w-7xl mx-auto px-4 text-center">
|
|
<h1 className="text-2xl font-bold text-red-600">Acesso Negado</h1>
|
|
<p className="text-gray-600 mt-2">
|
|
Apenas administradores podem acessar esta página.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 pt-20 sm:pt-24 md:pt-28 lg:pt-32 pb-8 sm:pb-12">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
{/* Header */}
|
|
<div className="mb-6 sm:mb-8">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div>
|
|
<h1 className="text-2xl sm:text-3xl font-serif font-bold text-brand-black">
|
|
Gestão de FOT
|
|
</h1>
|
|
<p className="text-sm sm:text-base text-gray-600 mt-1">
|
|
Gerencie todas as turmas FOT cadastradas
|
|
</p>
|
|
</div>
|
|
<Button
|
|
onClick={() => setShowForm(true)}
|
|
variant="secondary"
|
|
className="flex items-center space-x-2"
|
|
>
|
|
<Plus size={16} />
|
|
<span>Cadastro FOT</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Form Modal */}
|
|
{showForm && (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
<FotForm
|
|
onCancel={() => setShowForm(false)}
|
|
onSubmit={handleFormSubmit}
|
|
token={localStorage.getItem("token") || ""}
|
|
existingFots={existingFots}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Error State */}
|
|
{error && (
|
|
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg flex items-center gap-3 text-red-700">
|
|
<AlertTriangle size={20} />
|
|
<p>{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Courses Table */}
|
|
<div className="bg-white rounded-lg shadow border border-gray-200 overflow-hidden">
|
|
{isLoading ? (
|
|
<div className="p-12 text-center text-gray-500">
|
|
Carregando dados...
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
<thead className="bg-gray-50">
|
|
<tr>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
FOT
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Empresa
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Cursos
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Observações
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Instituição
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Ano Formatura
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Cidade
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Estado
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Gastos Captação
|
|
</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Pré Venda
|
|
</th>
|
|
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Ações
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
{fotList.length === 0 ? (
|
|
<tr>
|
|
<td
|
|
colSpan={11}
|
|
className="px-6 py-12 text-center text-gray-500"
|
|
>
|
|
<div className="flex flex-col items-center justify-center">
|
|
<Briefcase className="w-12 h-12 text-gray-300 mb-3" />
|
|
<p className="text-lg font-medium">
|
|
Nenhuma turma FOT encontrada
|
|
</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
fotList.map((item) => (
|
|
<tr
|
|
key={item.id}
|
|
className="hover:bg-gray-50 transition-colors"
|
|
>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm font-medium text-gray-900">
|
|
{item.fot || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-600">
|
|
{item.empresa_nome || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-600">
|
|
{item.curso_nome || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="text-sm text-gray-600 max-w-xs truncate">
|
|
{item.observacoes || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-600">
|
|
{item.instituicao || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-600">
|
|
{item.ano_formatura_label || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-600">
|
|
{item.cidade || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-600">
|
|
{item.estado || "-"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<div className="text-sm text-gray-600">
|
|
{item.gastos_captacao
|
|
? item.gastos_captacao.toLocaleString("pt-BR", {
|
|
style: "currency",
|
|
currency: "BRL",
|
|
})
|
|
: "R$ 0,00"}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<span
|
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${item.pre_venda
|
|
? "bg-green-100 text-green-800"
|
|
: "bg-gray-100 text-gray-800"
|
|
}`}
|
|
>
|
|
{item.pre_venda ? "Sim" : "Não"}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-center">
|
|
<button
|
|
className="p-1.5 text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
|
title="Editar"
|
|
>
|
|
<Edit size={16} />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div >
|
|
);
|
|
};
|