photum/frontend/contexts/DataContext.tsx
NANDO9322 28b76a0f54 feat(fot-management): implementação de ações editar/excluir e correção no mapeamento da agenda
- Implementadas ações de Editar e Excluir na página de Gestão de FOT
- Adicionado filtro de busca para FOTs
- Corrigido desalinhamento de colunas na tabela de Gestão de FOT
- Atualizado FotForm para suportar a edição de registros existentes
- Corrigido erro de renderização do React no Dashboard mapeando corretamente os objetos de atribuição
- Removidos dados de mock (INITIAL_EVENTS) e corrigido erro de referência nula no DataContext
- Adicionados métodos de atualização/exclusão ao apiService
2025-12-16 18:10:46 -03:00

1045 lines
31 KiB
TypeScript

import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
import { useAuth } from "./AuthContext";
import { getPendingUsers, approveUser as apiApproveUser, getProfessionals, assignProfessional as apiAssignProfessional, removeProfessional as apiRemoveProfessional, updateEventStatus as apiUpdateStatus, updateAssignmentStatus as apiUpdateAssignmentStatus } from "../services/apiService";
import {
EventData,
EventStatus,
EventType,
Institution,
Course,
User,
UserApprovalStatus,
UserRole,
Professional,
} from "../types";
// Initial Mock Data
const INITIAL_INSTITUTIONS: Institution[] = [
{
id: "inst-1",
name: "Universidade Federal do Rio Grande do Sul",
type: "Universidade Pública",
phone: "(51) 3308-3333",
email: "eventos@ufrgs.br",
address: {
street: "Av. Paulo Gama",
number: "110",
city: "Porto Alegre",
state: "RS",
zip: "90040-060",
},
description:
"Campus Central - Principais eventos realizados no Salão de Atos",
ownerId: "client-1",
},
];
const INITIAL_EVENTS: EventData[] = [
{
id: "1",
name: "Formatura Engenharia Civil",
date: "2025-12-05",
time: "19:00",
type: EventType.GRADUATION,
status: EventStatus.CONFIRMED,
address: {
street: "Av. das Hortênsias",
number: "1200",
city: "Gramado",
state: "RS",
zip: "95670-000",
},
briefing:
"Cerimônia de formatura com 120 formandos. Foco em fotos individuais e da turma.",
coverImage: "https://picsum.photos/id/1059/800/400",
contacts: [
{
id: "c1",
name: "Comissão de Formatura",
role: "Organizador",
phone: "51 99999-1111",
email: "formatura@email.com",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-1", "photographer-2"],
institutionId: "inst-1",
},
{
id: "2",
name: "Colação de Grau Medicina",
date: "2025-12-05",
time: "10:00",
type: EventType.COLATION,
status: EventStatus.CONFIRMED,
address: {
street: "Rua Olimpíadas",
number: "205",
city: "São Paulo",
state: "SP",
zip: "04551-000",
},
briefing:
"Colação de grau solene. Capturar juramento e entrega de diplomas.",
coverImage: "https://picsum.photos/id/3/800/400",
contacts: [
{
id: "c2",
name: "Secretaria Acadêmica",
role: "Coordenador",
phone: "11 98888-2222",
email: "academico@med.br",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-1"],
},
{
id: "3",
name: "Semana Acadêmica Direito",
date: "2025-12-05",
time: "14:00",
type: EventType.ACADEMIC_WEEK,
status: EventStatus.IN_PROGRESS,
address: {
street: "Av. Paulista",
number: "1500",
city: "São Paulo",
state: "SP",
zip: "01310-100",
},
briefing: "Palestras e painéis durante toda a semana. Cobertura de 3 dias.",
coverImage: "https://picsum.photos/id/10/800/400",
contacts: [],
checklist: [],
ownerId: "client-2",
photographerIds: ["photographer-2"],
},
{
id: "4",
name: "Defesa de Doutorado - Maria Silva",
date: "2025-12-05",
time: "15:30",
type: EventType.DEFENSE,
status: EventStatus.CONFIRMED,
address: {
street: "Rua Ramiro Barcelos",
number: "2600",
city: "Porto Alegre",
state: "RS",
zip: "90035-003",
},
briefing:
"Defesa de tese em sala fechada. Fotos discretas da apresentação e banca.",
coverImage: "https://picsum.photos/id/20/800/400",
contacts: [
{
id: "c3",
name: "Prof. João Santos",
role: "Orientador",
phone: "51 97777-3333",
email: "joao@univ.br",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-1"],
},
{
id: "5",
name: "Semana de Calouros 2026",
date: "2025-12-06",
time: "09:00",
type: EventType.FRESHMAN_WEEK,
status: EventStatus.PENDING_APPROVAL,
address: {
street: "Campus Universitário",
number: "s/n",
city: "Curitiba",
state: "PR",
zip: "80060-000",
},
briefing: "Recepção dos calouros com atividades de integração e gincanas.",
coverImage: "https://picsum.photos/id/30/800/400",
contacts: [],
checklist: [],
ownerId: "client-2",
photographerIds: [],
},
{
id: "6",
name: "Formatura Administração",
date: "2025-12-06",
time: "20:00",
type: EventType.GRADUATION,
status: EventStatus.CONFIRMED,
address: {
street: "Av. Ipiranga",
number: "6681",
city: "Porto Alegre",
state: "RS",
zip: "90619-900",
},
briefing: "Formatura noturna com jantar. Fotos da cerimônia e festa.",
coverImage: "https://picsum.photos/id/40/800/400",
contacts: [
{
id: "c4",
name: "Lucas Oliveira",
role: "Presidente da Comissão",
phone: "51 96666-4444",
email: "lucas@formatura.com",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-2"],
},
{
id: "7",
name: "Congresso de Tecnologia",
date: "2025-12-06",
time: "08:30",
type: EventType.SYMPOSIUM,
status: EventStatus.CONFIRMED,
address: {
street: "Av. das Nações Unidas",
number: "12901",
city: "São Paulo",
state: "SP",
zip: "04578-000",
},
briefing:
"Congresso com múltiplas salas. Cobrir palestrantes principais e stands.",
coverImage: "https://picsum.photos/id/50/800/400",
contacts: [
{
id: "c5",
name: "Eventos Tech",
role: "Organizadora",
phone: "11 95555-5555",
email: "contato@eventostech.com",
},
],
checklist: [],
ownerId: "client-2",
photographerIds: ["photographer-1", "photographer-3"],
},
{
id: "8",
name: "Campeonato Universitário de Futsal",
date: "2025-12-06",
time: "16:00",
type: EventType.SPORTS_EVENT,
status: EventStatus.CONFIRMED,
address: {
street: "Rua dos Esportes",
number: "500",
city: "Gramado",
state: "RS",
zip: "95670-100",
},
briefing: "Final do campeonato. Fotos dinâmicas da partida e premiação.",
coverImage: "https://picsum.photos/id/60/800/400",
contacts: [],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-3"],
},
{
id: "9",
name: "Colação de Grau Odontologia",
date: "2025-12-07",
time: "11:00",
type: EventType.COLATION,
status: EventStatus.PLANNING,
address: {
street: "Rua Voluntários da Pátria",
number: "89",
city: "Porto Alegre",
state: "RS",
zip: "90230-010",
},
briefing: "Cerimônia formal de colação. Fotos individuais e em grupo.",
coverImage: "https://picsum.photos/id/70/800/400",
contacts: [
{
id: "c6",
name: "Direção da Faculdade",
role: "Coordenador",
phone: "51 94444-6666",
email: "direcao@odonto.edu",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-2"],
},
{
id: "10",
name: "Festival Cultural Universitário",
date: "2025-12-07",
time: "18:00",
type: EventType.CULTURAL_EVENT,
status: EventStatus.CONFIRMED,
address: {
street: "Praça da República",
number: "s/n",
city: "São Paulo",
state: "SP",
zip: "01045-000",
},
briefing:
"Festival com apresentações musicais e teatrais. Cobertura completa.",
coverImage: "https://picsum.photos/id/80/800/400",
contacts: [],
checklist: [],
ownerId: "client-2",
photographerIds: ["photographer-1"],
},
{
id: "11",
name: "Defesa de Mestrado - Pedro Costa",
date: "2025-12-07",
time: "14:00",
type: EventType.DEFENSE,
status: EventStatus.CONFIRMED,
address: {
street: "Av. Bento Gonçalves",
number: "9500",
city: "Porto Alegre",
state: "RS",
zip: "91509-900",
},
briefing:
"Defesa de dissertação. Registro da apresentação e momento da aprovação.",
coverImage: "https://picsum.photos/id/90/800/400",
contacts: [],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-3"],
},
{
id: "12",
name: "Formatura Psicologia",
date: "2025-12-08",
time: "19:30",
type: EventType.GRADUATION,
status: EventStatus.CONFIRMED,
address: {
street: "Av. Protásio Alves",
number: "7000",
city: "Porto Alegre",
state: "RS",
zip: "91310-000",
},
briefing: "Formatura emotiva com homenagens. Foco em momentos especiais.",
coverImage: "https://picsum.photos/id/100/800/400",
contacts: [
{
id: "c7",
name: "Ana Paula",
role: "Formanda",
phone: "51 93333-7777",
email: "ana@email.com",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-1", "photographer-2"],
},
{
id: "13",
name: "Simpósio de Engenharia",
date: "2025-12-08",
time: "09:00",
type: EventType.SYMPOSIUM,
status: EventStatus.CONFIRMED,
address: {
street: "Av. Sertório",
number: "6600",
city: "Porto Alegre",
state: "RS",
zip: "91040-000",
},
briefing: "Apresentações técnicas e workshops. Cobrir painéis principais.",
coverImage: "https://picsum.photos/id/110/800/400",
contacts: [],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-2"],
},
{
id: "14",
name: "Torneio de Vôlei Universitário",
date: "2025-12-08",
time: "15:00",
type: EventType.SPORTS_EVENT,
status: EventStatus.IN_PROGRESS,
address: {
street: "Rua Faria Santos",
number: "100",
city: "Curitiba",
state: "PR",
zip: "80060-150",
},
briefing: "Semifinais e final. Fotos de ação e torcida.",
coverImage: "https://picsum.photos/id/120/800/400",
contacts: [],
checklist: [],
ownerId: "client-2",
photographerIds: ["photographer-3"],
},
{
id: "15",
name: "Colação de Grau Enfermagem",
date: "2025-12-09",
time: "10:30",
type: EventType.COLATION,
status: EventStatus.CONFIRMED,
address: {
street: "Rua São Manoel",
number: "963",
city: "São Paulo",
state: "SP",
zip: "01330-001",
},
briefing: "Colação com juramento de Florence Nightingale. Momento solene.",
coverImage: "https://picsum.photos/id/130/800/400",
contacts: [
{
id: "c8",
name: "Coordenação de Enfermagem",
role: "Coordenador",
phone: "11 92222-8888",
email: "coord@enf.br",
},
],
checklist: [],
ownerId: "client-2",
photographerIds: ["photographer-1"],
},
{
id: "16",
name: "Semana Acadêmica Biomedicina",
date: "2025-12-09",
time: "13:00",
type: EventType.ACADEMIC_WEEK,
status: EventStatus.PLANNING,
address: {
street: "Av. Independência",
number: "2293",
city: "Porto Alegre",
state: "RS",
zip: "90035-075",
},
briefing: "Palestras e atividades práticas. Cobertura de 2 dias.",
coverImage: "https://picsum.photos/id/140/800/400",
contacts: [],
checklist: [],
ownerId: "client-1",
photographerIds: [],
},
{
id: "17",
name: "Formatura Ciências Contábeis",
date: "2025-12-09",
time: "20:30",
type: EventType.GRADUATION,
status: EventStatus.CONFIRMED,
address: {
street: "Av. das Américas",
number: "3500",
city: "Gramado",
state: "RS",
zip: "95670-200",
},
briefing:
"Formatura elegante em hotel. Cobertura completa da cerimônia e recepção.",
coverImage: "https://picsum.photos/id/150/800/400",
contacts: [
{
id: "c9",
name: "Rodrigo Almeida",
role: "Tesoureiro",
phone: "51 91111-9999",
email: "rodrigo@turma.com",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-2", "photographer-3"],
},
{
id: "18",
name: "Defesa de TCC - Turma 2025",
date: "2025-12-09",
time: "16:30",
type: EventType.DEFENSE,
status: EventStatus.CONFIRMED,
address: {
street: "Rua Marquês do Pombal",
number: "2000",
city: "Porto Alegre",
state: "RS",
zip: "90540-000",
},
briefing:
"Múltiplas defesas sequenciais. Fotos rápidas de cada apresentação.",
coverImage: "https://picsum.photos/id/160/800/400",
contacts: [],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-1"],
},
{
id: "19",
name: "Festival de Música Universitária",
date: "2025-12-10",
time: "19:00",
type: EventType.CULTURAL_EVENT,
status: EventStatus.PENDING_APPROVAL,
address: {
street: "Parque da Redenção",
number: "s/n",
city: "Porto Alegre",
state: "RS",
zip: "90040-000",
},
briefing:
"Festival ao ar livre com várias bandas. Fotos de palco e público.",
coverImage: "https://picsum.photos/id/170/800/400",
contacts: [],
checklist: [],
ownerId: "client-2",
photographerIds: [],
},
{
id: "20",
name: "Colação de Grau Arquitetura",
date: "2025-12-10",
time: "11:30",
type: EventType.COLATION,
status: EventStatus.CONFIRMED,
address: {
street: "Av. Borges de Medeiros",
number: "1501",
city: "Gramado",
state: "RS",
zip: "95670-300",
},
briefing: "Cerimônia especial com exposição de projetos. Fotos criativas.",
coverImage: "https://picsum.photos/id/180/800/400",
contacts: [
{
id: "c10",
name: "Atelier Arquitetura",
role: "Escritório Parceiro",
phone: "51 90000-1010",
email: "contato@atelier.arq",
},
],
checklist: [],
ownerId: "client-1",
photographerIds: ["photographer-3"],
},
];
// Initial Mock Courses
const INITIAL_COURSES: Course[] = [
{
id: "course-1",
name: "Engenharia Civil 2025",
institutionId: "inst-1",
year: 2025,
semester: 2,
graduationType: "Bacharelado",
createdAt: new Date().toISOString(),
createdBy: "admin-1",
isActive: true,
},
{
id: "course-2",
name: "Medicina - Turma A 2025",
institutionId: "inst-1",
year: 2025,
semester: 1,
graduationType: "Bacharelado",
createdAt: new Date().toISOString(),
createdBy: "admin-1",
isActive: true,
},
{
id: "course-3",
name: "Direito Noturno 2025",
institutionId: "inst-1",
year: 2025,
semester: 2,
graduationType: "Bacharelado",
createdAt: new Date().toISOString(),
createdBy: "admin-1",
isActive: true,
},
];
interface DataContextType {
events: EventData[];
institutions: Institution[];
courses: Course[];
pendingUsers: User[];
addEvent: (event: EventData) => void;
updateEventStatus: (id: string, status: EventStatus) => void;
assignPhotographer: (eventId: string, photographerId: string) => void;
getEventsByRole: (userId: string, role: string) => EventData[];
addInstitution: (institution: Institution) => void;
updateInstitution: (id: string, institution: Partial<Institution>) => void;
getInstitutionsByUserId: (userId: string) => Institution[];
getInstitutionById: (id: string) => Institution | undefined;
addCourse: (course: Course) => void;
updateCourse: (id: string, course: Partial<Course>) => void;
getCoursesByInstitutionId: (institutionId: string) => Course[];
getActiveCoursesByInstitutionId: (institutionId: string) => Course[];
getCourseById: (id: string) => Course | undefined;
registerPendingUser: (userData: { id: string; name: string; email: string; phone: string; registeredInstitution?: string }) => void;
approveUser: (userId: string) => void;
rejectUser: (userId: string) => void;
professionals: Professional[];
respondToAssignment: (eventId: string, status: string, reason?: string) => Promise<void>;
}
const DataContext = createContext<DataContextType | undefined>(undefined);
export const DataProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const { token, user } = useAuth(); // Consume Auth Context
const [events, setEvents] = useState<EventData[]>([]);
const [institutions, setInstitutions] =
useState<Institution[]>(INITIAL_INSTITUTIONS);
const [courses, setCourses] = useState<Course[]>(INITIAL_COURSES);
const [pendingUsers, setPendingUsers] = useState<User[]>([]);
const [professionals, setProfessionals] = useState<Professional[]>([]);
// Fetch events from API
useEffect(() => {
const fetchEvents = async () => {
// Use token from context or fallback to localStorage if context not ready (though context is preferred sources of truth)
const visibleToken = token || localStorage.getItem("token");
if (visibleToken) {
try {
// Import dynamic to avoid circular dependency if any, or just use imported service
const { getAgendas } = await import("../services/apiService");
const result = await getAgendas(visibleToken);
console.log("Raw Agenda Data:", result.data); // Debug logging
if (result.data) {
const mappedEvents: EventData[] = result.data.map((e: any) => ({
id: e.id,
name: e.observacoes_evento || e.tipo_evento_nome || "Evento sem nome", // Fallback mapping
date: e.data_evento ? e.data_evento.split('T')[0] : "",
time: e.horario || "00:00",
type: (e.tipo_evento_nome || "Outro") as EventType, // Map string to enum if possible, or keep string
status: EventStatus.PENDING_APPROVAL,
address: {
street: e.endereco ? e.endereco.split(',')[0] : "",
number: e.endereco ? e.endereco.split(',')[1]?.split('-')[0]?.trim() || "" : "",
city: e.endereco ? e.endereco.split('-')[1]?.split('/')[0]?.trim() || "" : "",
state: e.endereco ? e.endereco.split('/')[1]?.trim() || "" : "",
zip: "",
mapLink: e.local_evento?.startsWith('http') ? e.local_evento : undefined
},
briefing: e.observacoes_evento || "",
coverImage: "https://picsum.photos/id/10/800/400", // Placeholder
contacts: [], // TODO: fetch contacts if needed
checklist: [],
ownerId: e.user_id || "unknown",
photographerIds: Array.isArray(e.assigned_professionals)
? e.assigned_professionals.map((a: any) => a.professional_id)
: [],
institutionId: "", // TODO
attendees: e.qtd_formandos,
fotId: e.fot_id, // UUID
// Joined Fields
fot: e.fot_numero ?? e.fot_id, // Show Number if available (even 0), else ID
curso: e.curso_nome,
instituicao: e.instituicao,
anoFormatura: e.ano_semestre,
empresa: e.empresa_nome,
observacoes: e.observacoes_fot,
typeId: e.tipo_evento_id,
assignments: Array.isArray(e.assigned_professionals)
? e.assigned_professionals.map((a: any) => ({
professionalId: a.professional_id,
status: a.status,
rejectionReason: a.motivo_rejeicao
}))
: [],
}));
setEvents(mappedEvents);
} else {
setEvents([]);
}
} catch (error) {
console.error("Failed to fetch events", error);
}
}
};
fetchEvents();
}, [token]); // React to token change
// Fetch pending users from API
useEffect(() => {
const fetchUsers = async () => {
const token = localStorage.getItem('token');
if (token) {
try {
const result = await getPendingUsers(token);
if (result.data) {
const mappedUsers: User[] = result.data.map((u: any) => {
// Map backend roles to frontend enum
let mappedRole = UserRole.EVENT_OWNER;
if (u.role === 'profissional') mappedRole = UserRole.PHOTOGRAPHER;
else if (u.role === 'empresa') mappedRole = UserRole.BUSINESS_OWNER;
else if (u.role === 'admin') mappedRole = UserRole.SUPERADMIN;
else if (u.role === 'cliente') mappedRole = UserRole.EVENT_OWNER;
return {
id: u.id,
name: u.name || u.email.split('@')[0],
email: u.email,
phone: u.phone || '',
role: mappedRole,
approvalStatus: UserApprovalStatus.PENDING,
createdAt: u.created_at,
registeredInstitution: mappedRole === UserRole.EVENT_OWNER ? 'N/A' : undefined,
};
});
setPendingUsers(mappedUsers);
}
} catch (error) {
console.error("Failed to fetch pending users", error);
}
}
};
fetchUsers();
}, []);
// Fetch professionals
useEffect(() => {
const fetchProfs = async () => {
const token = localStorage.getItem('token');
console.log("[DEBUG] Fetching Professionals...", { token });
if (token) {
try {
const result = await getProfessionals(token);
console.log("[DEBUG] Fetch Professionals Result:", result);
if (result.data) {
const mappedProfs: Professional[] = result.data.map((p: any) => ({
id: p.id,
usuarioId: p.usuario_id,
name: p.nome,
email: p.email || "",
role: p.funcao_nome || "Fotógrafo",
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(p.nome)}&background=random`, // Fallback avatar
phone: p.whatsapp,
availability: {}, // Default empty availability
}));
setProfessionals(mappedProfs);
}
} catch (error) {
console.error("Failed to fetch professionals", error);
}
} else {
console.warn("[DEBUG] No token found for fetching professionals");
}
};
fetchProfs();
}, [token]);
const addEvent = async (event: EventData) => {
const token = localStorage.getItem("@Photum:token");
if (!token) {
console.error("No token found");
// Fallback for offline/mock
setEvents((prev) => [event, ...prev]);
return;
}
try {
// Map frontend fields (camelCase) to backend fields (snake_case)
const payload = {
fot_id: event.fotId,
data_evento: event.date, // "YYYY-MM-DD" is acceptable
tipo_evento_id: event.typeId,
observacoes_evento: event.name, // "Observações do Evento" maps to name in EventForm
// local_evento: event.address.street + ", " + event.address.number, // Or map separate fields if needed
local_evento: event.address.mapLink || "Local a definir", // using mapLink or some string
endereco: `${event.address.street}, ${event.address.number}, ${event.address.city} - ${event.address.state}`,
horario: event.startTime,
// Defaulting missing counts to 0 for now as they are not in the simplified form
qtd_formandos: event.attendees ? parseInt(String(event.attendees)) : 0,
qtd_fotografos: 0,
qtd_recepcionistas: 0,
qtd_cinegrafistas: 0,
qtd_estudios: 0,
qtd_ponto_foto: 0,
qtd_ponto_id: 0,
qtd_ponto_decorado: 0,
qtd_pontos_led: 0,
qtd_plataforma_360: 0,
status_profissionais: "AGUARDANDO", // Will be calculated by backend anyway
foto_faltante: 0,
recep_faltante: 0,
cine_faltante: 0,
logistica_observacoes: "",
pre_venda: false
};
const result = await import("../services/apiService").then(m => m.createAgenda(token, payload));
if (result.data) {
// Success
console.log("Agenda criada:", result.data);
const newEvent = { ...event, id: result.data.id, status: EventStatus.PENDING_APPROVAL };
setEvents((prev) => [newEvent, ...prev]);
} else {
console.error("Erro ao criar agenda API:", result.error);
// Fallback or Toast?
// We will optimistically add it locally or throw
}
} catch (err) {
console.error("Exception creating agenda:", err);
}
};
const updateEventStatus = async (id: string, status: EventStatus) => {
const token = localStorage.getItem('token');
if (token) {
try {
await apiUpdateStatus(token, id, status);
setEvents((prev) => prev.map((e) => (e.id === id ? { ...e, status } : e)));
} catch (error) {
console.error("Failed to update status", error);
}
} else {
// Fallback
setEvents((prev) => prev.map((e) => (e.id === id ? { ...e, status } : e)));
}
};
const assignPhotographer = async (eventId: string, photographerId: string) => {
const token = localStorage.getItem('token');
const event = events.find(e => e.id === eventId);
if (!event) return;
const current = event.photographerIds || [];
const isRemoving = current.includes(photographerId);
if (token) {
try {
if (isRemoving) {
await apiRemoveProfessional(token, eventId, photographerId);
} else {
await apiAssignProfessional(token, eventId, photographerId);
}
} catch (error) {
console.error("Failed to assign/remove professional", error);
return; // Don't update state if API fails
}
}
setEvents((prev) =>
prev.map((e) => {
if (e.id === eventId) {
const current = e.photographerIds || [];
if (current.includes(photographerId)) {
// Remove
return { ...e, photographerIds: current.filter(id => id !== photographerId) };
} else {
// Add
return { ...e, photographerIds: [...current, photographerId] };
}
}
return e;
})
);
};
const getEventsByRole = (userId: string, role: string) => {
if (role === "SUPERADMIN" || role === "BUSINESS_OWNER") {
return events;
}
if (role === "EVENT_OWNER") {
return events.filter((e) => e.ownerId === userId);
}
if (role === "PHOTOGRAPHER") {
const professional = professionals.find((p) => p.usuarioId === userId);
if (!professional) return [];
const professionalId = professional.id;
return events.filter((e) => e.photographerIds.includes(professionalId));
}
return [];
};
const addInstitution = (institution: Institution) => {
setInstitutions((prev) => [...prev, institution]);
};
const updateInstitution = (id: string, updatedData: Partial<Institution>) => {
setInstitutions((prev) =>
prev.map((inst) => (inst.id === id ? { ...inst, ...updatedData } : inst))
);
};
const getInstitutionsByUserId = (userId: string) => {
return institutions.filter((inst) => inst.ownerId === userId);
};
const getInstitutionById = (id: string) => {
return institutions.find((inst) => inst.id === id);
};
const addCourse = (course: Course) => {
setCourses((prev) => [...prev, course]);
};
const updateCourse = (id: string, updatedData: Partial<Course>) => {
setCourses((prev) =>
prev.map((course) =>
course.id === id ? { ...course, ...updatedData } : course
)
);
};
const getCoursesByInstitutionId = (institutionId: string) => {
return courses.filter((course) => course.institutionId === institutionId);
};
const getActiveCoursesByInstitutionId = (institutionId: string) => {
return courses.filter(
(course) => course.institutionId === institutionId && course.isActive
);
};
const getCourseById = (id: string) => {
return courses.find((course) => course.id === id);
};
// Funções para gerenciar usuários pendentes
const registerPendingUser = (userData: {
id: string;
name: string;
email: string;
phone: string;
registeredInstitution?: string
}) => {
const newUser: User = {
id: userData.id,
name: userData.name,
email: userData.email,
phone: userData.phone,
role: UserRole.EVENT_OWNER,
approvalStatus: UserApprovalStatus.PENDING,
registeredInstitution: userData.registeredInstitution,
createdAt: new Date().toISOString(),
};
setPendingUsers((prev) => [...prev, newUser]);
};
const approveUser = async (userId: string) => {
const token = localStorage.getItem('token');
if (token) {
try {
await apiApproveUser(userId, token);
setPendingUsers((prev) => prev.filter(user => user.id !== userId));
} catch (error) {
console.error("Failed to approve user", error);
}
} else {
// Fallback for mock/testing
setPendingUsers((prev) =>
prev.map((user) =>
user.id === userId
? { ...user, approvalStatus: UserApprovalStatus.APPROVED }
: user
)
);
}
};
const rejectUser = (userId: string) => {
setPendingUsers((prev) =>
prev.map((user) =>
user.id === userId
? { ...user, approvalStatus: UserApprovalStatus.REJECTED }
: user
)
);
};
return (
<DataContext.Provider
value={{
events,
institutions,
courses,
pendingUsers,
professionals,
respondToAssignment: async (eventId, status, reason) => {
const token = localStorage.getItem('token');
if (!token || !user) return;
const professional = professionals.find(p => p.usuarioId === user.id);
if (!professional) return;
await apiUpdateAssignmentStatus(token, eventId, professional.id, status, reason);
// Re-fetch events to update status locally efficiently (or update local state)
// For simplicity, let's update local state
setEvents((prev) =>
prev.map((e) => {
if (e.id === eventId) {
const updatedAssignments = e.assignments?.map(a =>
a.professionalId === professional.id ? { ...a, status: status as any, reason } : a
) || [];
// If it wasn't in assignments (unlikely if responding), simple update
return { ...e, assignments: updatedAssignments };
}
return e;
})
);
},
addEvent,
updateEventStatus,
assignPhotographer,
getEventsByRole,
addInstitution,
updateInstitution,
getInstitutionsByUserId,
getInstitutionById,
addCourse,
updateCourse,
getCoursesByInstitutionId,
getActiveCoursesByInstitutionId,
getCourseById,
registerPendingUser,
approveUser,
rejectUser,
}}
>
{children}
</DataContext.Provider>
);
};
export const useData = () => {
const context = useContext(DataContext);
if (!context) throw new Error("useData must be used within a DataProvider");
return context;
};