- Renomear 'Equipe & Fotógrafos' para 'Equipe' no título e navbar - Adicionar suporte para 3 tipos de profissionais: Fotógrafo, Cinegrafista, Recepcionista - Implementar cards estatísticos separados por função profissional - Adicionar filtros por função (Fotógrafos, Cinegrafistas, Recepcionistas) - Adicionar filtros por status (Disponível, Em Evento, Inativo) - Transformar cards em tabela responsiva com colunas: Nome, Função Profissional, Disponibilidade - Expandir interface Professional com campos completos do Excel: * Endereço completo (rua, número, complemento, bairro, cidade, UF) * Dados bancários (banco, agência, conta/pix, tipo cartão, titular) * Recursos (carro disponível, possui estúdio, quantidade) * Sistema de avaliações detalhado (6 critérios + média) * Valores (tabela free, extra no cachê) * Observações - Redesenhar modal 'Adicionar Profissional' com formulário extenso organizado em seções - Atualizar modal de detalhes com todas as novas informações - Adicionar ícones específicos por função (Camera, Video, UserCheck) - Remover fotos da tabela mantendo apenas informações essenciais
378 lines
15 KiB
TypeScript
378 lines
15 KiB
TypeScript
import React, { useState } from "react";
|
|
import { useData } from "../contexts/DataContext";
|
|
import { useAuth } from "../contexts/AuthContext";
|
|
import { UserRole, Course } from "../types";
|
|
import { CourseForm } from "../components/CourseForm";
|
|
import {
|
|
GraduationCap,
|
|
Plus,
|
|
Building2,
|
|
ChevronRight,
|
|
Edit,
|
|
Trash2,
|
|
CheckCircle,
|
|
XCircle,
|
|
} from "lucide-react";
|
|
import { Button } from "../components/Button";
|
|
|
|
export const CourseManagement: React.FC = () => {
|
|
const { user } = useAuth();
|
|
const {
|
|
institutions,
|
|
courses,
|
|
addCourse,
|
|
updateCourse,
|
|
getCoursesByInstitutionId,
|
|
} = useData();
|
|
|
|
const [selectedInstitution, setSelectedInstitution] = useState<string | null>(
|
|
null
|
|
);
|
|
const [showCourseForm, setShowCourseForm] = useState(false);
|
|
const [editingCourse, setEditingCourse] = useState<Course | undefined>(
|
|
undefined
|
|
);
|
|
|
|
// Verificar se é admin
|
|
const isAdmin =
|
|
user?.role === UserRole.SUPERADMIN ||
|
|
user?.role === UserRole.BUSINESS_OWNER;
|
|
|
|
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>
|
|
);
|
|
}
|
|
|
|
const selectedInstitutionData = institutions.find(
|
|
(inst) => inst.id === selectedInstitution
|
|
);
|
|
const institutionCourses = selectedInstitution
|
|
? getCoursesByInstitutionId(selectedInstitution)
|
|
: [];
|
|
|
|
const handleAddCourse = () => {
|
|
setEditingCourse(undefined);
|
|
setShowCourseForm(true);
|
|
};
|
|
|
|
const handleEditCourse = (course: Course) => {
|
|
setEditingCourse(course);
|
|
setShowCourseForm(true);
|
|
};
|
|
|
|
const handleSubmitCourse = (courseData: Partial<Course>) => {
|
|
if (editingCourse) {
|
|
updateCourse(editingCourse.id, courseData);
|
|
} else {
|
|
const newCourse: Course = {
|
|
id: `course-${Date.now()}`,
|
|
name: courseData.name!,
|
|
institutionId: selectedInstitution || courseData.institutionId!,
|
|
year: courseData.year!,
|
|
semester: courseData.semester,
|
|
graduationType: courseData.graduationType!,
|
|
createdAt: new Date().toISOString(),
|
|
createdBy: user?.id || "",
|
|
isActive: courseData.isActive !== false,
|
|
};
|
|
addCourse(newCourse);
|
|
}
|
|
setShowCourseForm(false);
|
|
setEditingCourse(undefined);
|
|
};
|
|
|
|
const handleToggleActive = (course: Course) => {
|
|
updateCourse(course.id, { isActive: !course.isActive });
|
|
};
|
|
|
|
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 space-x-3 mb-2">
|
|
<h1 className="text-2xl sm:text-3xl font-serif font-bold text-brand-black">
|
|
Gestão de Cursos e Turmas
|
|
</h1>
|
|
</div>
|
|
<p className="text-sm sm:text-base text-gray-600">
|
|
Cadastre e gerencie os cursos/turmas disponíveis em cada
|
|
universidade
|
|
</p>
|
|
</div>
|
|
|
|
{/* Course Form Modal */}
|
|
{showCourseForm && (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 overflow-y-auto">
|
|
<CourseForm
|
|
onCancel={() => {
|
|
setShowCourseForm(false);
|
|
setEditingCourse(undefined);
|
|
}}
|
|
onSubmit={handleSubmitCourse}
|
|
initialData={editingCourse}
|
|
userId={user?.id || ""}
|
|
institutions={institutions}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Left Panel - Institutions Table */}
|
|
<div className="bg-white rounded-lg shadow border border-gray-200">
|
|
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-2">
|
|
<Building2 className="text-brand-gold h-5 w-5" />
|
|
<h2 className="text-lg font-semibold text-gray-900">
|
|
Universidades Cadastradas
|
|
</h2>
|
|
</div>
|
|
<span className="text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded-full">
|
|
{institutions.length}{" "}
|
|
{institutions.length === 1 ? "instituição" : "instituições"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Universidade
|
|
</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Tipo
|
|
</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Cursos
|
|
</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Ação
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200">
|
|
{institutions.length === 0 ? (
|
|
<tr>
|
|
<td
|
|
colSpan={4}
|
|
className="px-4 py-8 text-center text-gray-500"
|
|
>
|
|
Nenhuma universidade cadastrada
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
institutions.map((institution) => {
|
|
const coursesCount = getCoursesByInstitutionId(
|
|
institution.id
|
|
).length;
|
|
const isSelected = selectedInstitution === institution.id;
|
|
|
|
return (
|
|
<tr
|
|
key={institution.id}
|
|
className={`hover:bg-gray-50 transition-colors cursor-pointer ${
|
|
isSelected
|
|
? "bg-blue-50 border-l-4 border-brand-gold"
|
|
: ""
|
|
}`}
|
|
onClick={() =>
|
|
setSelectedInstitution(
|
|
isSelected ? null : institution.id
|
|
)
|
|
}
|
|
>
|
|
<td className="px-4 py-4">
|
|
<div className="text-sm font-medium text-gray-900">
|
|
{institution.name}
|
|
</div>
|
|
<div className="text-xs text-gray-500 mt-0.5">
|
|
{institution.address?.city},{" "}
|
|
{institution.address?.state}
|
|
</div>
|
|
</td>
|
|
<td className="px-4 py-4">
|
|
<span className="text-xs font-medium text-gray-600 bg-gray-100 px-2 py-1 rounded">
|
|
{institution.type}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-4 text-center">
|
|
<span
|
|
className={`inline-flex items-center justify-center w-8 h-8 rounded-full text-sm font-semibold ${
|
|
coursesCount > 0
|
|
? "bg-green-100 text-green-700"
|
|
: "bg-gray-100 text-gray-500"
|
|
}`}
|
|
>
|
|
{coursesCount}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-4 text-center">
|
|
<ChevronRight
|
|
className={`inline-block transition-transform ${
|
|
isSelected
|
|
? "rotate-90 text-brand-gold"
|
|
: "text-gray-400"
|
|
}`}
|
|
size={20}
|
|
/>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Panel - Courses Table */}
|
|
<div className="bg-white rounded-lg shadow border border-gray-200">
|
|
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-900">
|
|
{selectedInstitutionData ? (
|
|
<>Cursos - {selectedInstitutionData.name}</>
|
|
) : (
|
|
<>Cursos da Universidade</>
|
|
)}
|
|
</h2>
|
|
{selectedInstitutionData && (
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
{institutionCourses.length}{" "}
|
|
{institutionCourses.length === 1
|
|
? "curso cadastrado"
|
|
: "cursos cadastrados"}
|
|
</p>
|
|
)}
|
|
</div>
|
|
{selectedInstitution && (
|
|
<Button
|
|
onClick={handleAddCourse}
|
|
variant="secondary"
|
|
className="flex items-center space-x-2"
|
|
>
|
|
<Plus size={16} />
|
|
<span>Cadastrar Turma</span>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
{!selectedInstitution ? (
|
|
<div className="px-6 py-12 text-center">
|
|
<Building2 className="mx-auto h-12 w-12 text-gray-300 mb-3" />
|
|
<p className="text-gray-500 text-sm">
|
|
Selecione uma universidade para ver seus cursos
|
|
</p>
|
|
</div>
|
|
) : institutionCourses.length === 0 ? (
|
|
<div className="px-6 py-12 text-center">
|
|
<GraduationCap className="mx-auto h-12 w-12 text-gray-300 mb-3" />
|
|
<p className="text-gray-500 text-sm mb-4">
|
|
Nenhum curso cadastrado nesta universidade
|
|
</p>
|
|
<Button onClick={handleAddCourse} variant="secondary">
|
|
<Plus size={16} className="mr-2" />
|
|
Cadastrar Primeiro Curso
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Curso/Turma
|
|
</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Tipo
|
|
</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Período
|
|
</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Status
|
|
</th>
|
|
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Ações
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200">
|
|
{institutionCourses.map((course) => (
|
|
<tr key={course.id} className="hover:bg-gray-50">
|
|
<td className="px-4 py-4">
|
|
<div className="text-sm font-medium text-gray-900">
|
|
{course.name}
|
|
</div>
|
|
</td>
|
|
<td className="px-4 py-4">
|
|
<span className="text-xs font-medium text-gray-600 bg-gray-100 px-2 py-1 rounded">
|
|
{course.graduationType}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-4 text-center">
|
|
<span className="text-xs text-gray-600">
|
|
{course.year}/{course.semester}º
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-4 text-center">
|
|
<button
|
|
onClick={() => handleToggleActive(course)}
|
|
className="inline-flex items-center space-x-1"
|
|
>
|
|
{course.isActive ? (
|
|
<>
|
|
<CheckCircle
|
|
size={16}
|
|
className="text-green-500"
|
|
/>
|
|
<span className="text-xs text-green-700 font-medium">
|
|
Ativo
|
|
</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<XCircle size={16} className="text-gray-400" />
|
|
<span className="text-xs text-gray-500 font-medium">
|
|
Inativo
|
|
</span>
|
|
</>
|
|
)}
|
|
</button>
|
|
</td>
|
|
<td className="px-4 py-4">
|
|
<div className="flex items-center justify-center space-x-2">
|
|
<button
|
|
onClick={() => handleEditCourse(course)}
|
|
className="p-1.5 text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
|
title="Editar curso"
|
|
>
|
|
<Edit size={16} />
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|