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, updateAgenda as apiUpdateAgenda } 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) => void; getInstitutionsByUserId: (userId: string) => Institution[]; getInstitutionById: (id: string) => Institution | undefined; addCourse: (course: Course) => void; updateCourse: (id: string, course: Partial) => 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; updateEventDetails: (id: string, data: any) => Promise; functions: { id: string; nome: string }[]; isLoading: boolean; refreshEvents: () => Promise; } const DataContext = createContext(undefined); export const DataProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { const { token, user } = useAuth(); // Consume Auth Context const [events, setEvents] = useState([]); const [institutions, setInstitutions] = useState(INITIAL_INSTITUTIONS); const [courses, setCourses] = useState(INITIAL_COURSES); const [isLoading, setIsLoading] = useState(false); const [pendingUsers, setPendingUsers] = useState([]); const [professionals, setProfessionals] = useState([]); const [functions, setFunctions] = useState<{ id: string; nome: string }[]>([]); const [refreshTrigger, setRefreshTrigger] = useState(0); // 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) { setIsLoading(true); try { // Import dynamic to avoid circular dependency if any, or just use imported service const { getAgendas, getFunctions } = await import("../services/apiService"); // Fetch Functions (Roles) getFunctions().then(res => { if (res.data) setFunctions(res.data); }); const result = await getAgendas(visibleToken); if (result.data) { // Map backend status to frontend EventStatus const mapStatus = (backendStatus: string): EventStatus => { const statusMap: Record = { "Pendente": EventStatus.PENDING_APPROVAL, "Aguardando Aprovação": EventStatus.PENDING_APPROVAL, "PENDING_APPROVAL": EventStatus.PENDING_APPROVAL, "Confirmado": EventStatus.CONFIRMED, "CONFIRMED": EventStatus.CONFIRMED, "Em Planejamento": EventStatus.PLANNING, "PLANNING": EventStatus.PLANNING, "Em Execução": EventStatus.IN_PROGRESS, "IN_PROGRESS": EventStatus.IN_PROGRESS, "Entregue": EventStatus.DELIVERED, "DELIVERED": EventStatus.DELIVERED, "Arquivado": EventStatus.ARCHIVED, "ARCHIVED": EventStatus.ARCHIVED, }; return statusMap[backendStatus] || EventStatus.PENDING_APPROVAL; }; 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: mapStatus(e.status), // Map from backend status with fallback 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 // Resource Mapping qtdFormandos: e.qtd_formandos, qtdFotografos: e.qtd_fotografos, qtdRecepcionistas: e.qtd_recepcionistas, qtdCinegrafistas: e.qtd_cinegrafistas, qtdEstudios: e.qtd_estudios, qtdPontosFoto: e.qtd_ponto_foto, qtdPontosDecorados: e.qtd_ponto_decorado, qtdPontosLed: e.qtd_pontos_led, qtdPlataforma360: e.qtd_plataforma_360, // 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, empresaId: e.empresa_id, // Ensure ID is passed to frontend observacoes: e.observacoes_fot, typeId: e.tipo_evento_id, local_evento: e.local_evento, // Added local_evento mapping fot_finalizada: e.fot_finalizada, // Mapped from backend fot_pre_venda: e.fot_pre_venda, // Mapped from backend pre_venda: e.fot_pre_venda, // Fallback/Alias assignments: Array.isArray(e.assigned_professionals) ? e.assigned_professionals.map((a: any) => ({ professionalId: a.professional_id, status: a.status, reason: a.motivo_rejeicao, funcaoId: a.funcao_id, is_coordinator: a.is_coordinator })) : [], logisticaNotificacaoEnviadaEm: e.logistica_notificacao_enviada_em, })); setEvents(mappedEvents); } else { setEvents([]); } } catch (error) { console.error("Failed to fetch events", error); } finally { setIsLoading(false); } } }; fetchEvents(); }, [token, refreshTrigger]); // React to token change and manual refresh const refreshEvents = async () => { setRefreshTrigger(prev => prev + 1); }; // 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'); if (token) { try { const result = await getProfessionals(token); if (result.data) { const mappedProfs: Professional[] = result.data.map((p: any) => ({ id: p.id, usuarioId: p.usuario_id, nome: p.nome, name: p.nome, // Keep for legacy Dashboard usage email: p.email || "", funcao_profissional_id: p.funcao_profissional_id, role: p.funcao_profissional || p.funcao_nome || "Fotógrafo", avatar: p.avatar_url || `https://ui-avatars.com/api/?name=${encodeURIComponent(p.nome)}&background=random`, phone: p.whatsapp, // Detailed fields endereco: p.endereco, cidade: p.cidade, uf: p.uf, cep: p.cep, whatsapp: p.whatsapp, cpf_cnpj_titular: p.cpf_cnpj_titular, banco: p.banco, agencia: p.agencia, conta_pix: p.conta_pix, carro_disponivel: p.carro_disponivel, tem_estudio: p.tem_estudio, qtd_estudio: p.qtd_estudio, tipo_cartao: p.tipo_cartao, observacao: p.observacao, // Ratings qual_tec: p.qual_tec, educacao_simpatia: p.educacao_simpatia, desempenho_evento: p.desempenho_evento, disp_horario: p.disp_horario, media: p.media, tabela_free: p.tabela_free, extra_por_equipamento: p.extra_por_equipamento, equipamentos: p.equipamentos, // Multi-function support functions: p.functions || [], availability: {}, // Default empty availability })); setProfessionals(mappedProfs); } } catch (error) { console.error("Failed to fetch professionals", error); } } }; fetchProfs(); }, [token]); const addEvent = async (event: any) => { const token = localStorage.getItem("token"); if (!token) { console.error("No token found"); throw new Error("Usuário não autenticado"); } try { // Check if payload is already mapped (snake_case) or needs mapping (camelCase) let payload; if (event.fot_id && event.tipo_evento_id) { // Already snake_case (from EventForm payload) payload = event; } else { // Legacy camelCase mapping payload = { fot_id: event.fotId, data_evento: event.date + "T" + (event.time || "00:00") + ":00Z", tipo_evento_id: event.typeId, observacoes_evento: event.name, local_evento: event.address?.mapLink || "Local a definir", endereco: event.address ? `${event.address.street}, ${event.address.number}, ${event.address.city} - ${event.address.state}` : "", horario: event.startTime, 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", foto_faltante: 0, recep_faltante: 0, cine_faltante: 0, logistica_observacoes: "", pre_venda: false }; } console.log("[DEBUG] addEvent payload:", payload); const result = await import("../services/apiService").then(m => m.createAgenda(token, payload)); if (result.data) { console.log("Agenda criada:", result.data); // Force reload to ensure complete data consistency window.location.href = '/painel'; } else { console.error("Erro ao criar agenda API:", result.error); throw new Error(result.error || "Erro ao criar agenda"); } } catch (err: any) { console.error("Exception creating agenda:", err); throw err; // Re-throw so EventForm knows it failed } }; 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, funcaoId?: 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, funcaoId); } } 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 || []; const currentAssignments = e.assignments || []; if (current.includes(photographerId)) { // Remove return { ...e, photographerIds: current.filter(id => id !== photographerId), assignments: currentAssignments.filter(a => a.professionalId !== photographerId) }; } else { // Add // Import AssignmentStatus if needed or use string "PENDENTE" matching the type return { ...e, photographerIds: [...current, photographerId], assignments: [...currentAssignments, { professionalId: photographerId, status: "PENDENTE" as any, funcaoId }] }; } } return e; }) ); }; const getEventsByRole = (userId: string, role: string) => { if (role === "SUPERADMIN" || role === "BUSINESS_OWNER" || role === "RESEARCHER" || role === "AGENDA_VIEWER" || role === UserRole.AGENDA_VIEWER) { return events; } if (role === "EVENT_OWNER") { // Check if logged user has company linked and matches requested user if (user && user.id === userId && user.empresaId) { return events.filter(e => e.empresaId === user.empresaId); } 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) => { // Incluir apenas eventos onde o fotógrafo está designado if (!e.photographerIds.includes(professionalId)) return false; // Excluir eventos que foram rejeitados pelo fotógrafo const assignment = (e.assignments || []).find(a => a.professionalId === professionalId); return !assignment || assignment.status !== "REJEITADO"; }); } return []; }; const addInstitution = (institution: Institution) => { setInstitutions((prev) => [...prev, institution]); }; const updateInstitution = (id: string, updatedData: Partial) => { 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) => { 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 ( { const token = localStorage.getItem('token'); if (!token || !user) return; const professional = professionals.find(p => p.usuarioId === user.id); if (!professional) return; try { // Check if `apiUpdateAssignmentStatus` returns { error } object or throws // Based on other calls (e.g. line 1089), it likely returns an object. const result = await apiUpdateAssignmentStatus(token, eventId, professional.id, status, reason); if (result && result.error) { alert("Erro ao atualizar status: " + result.error); return; } // Only update state if successful 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 ) || []; return { ...e, assignments: updatedAssignments }; } return e; }) ); } catch (error: any) { console.error("Failed to update status", error); alert("Erro de conexão ou servidor: " + (error.message || error)); } }, updateEventDetails: async (id, data) => { const token = localStorage.getItem("token"); if (!token) return; const result = await apiUpdateAgenda(token, id, data); if (result.error) { throw new Error(result.error); } // Re-fetch logic to ensure state consistency // Re-implementing simplified fetch logic here or we can trigger a reload. // Since we are in DataContext, we can call a fetch function if we extract it. // But to be safe and quick: try { const result = await import("../services/apiService").then(m => m.getAgendas(token)); if (result.data) { // Re-map events logic from useEffect... // This duplication is painful. // Alternative: window.location.reload() in Dashboard. // But let's assume the user navigates away or we do a simple local merge for Key Fields used in List. setEvents(prev => prev.map(evt => { if (evt.id === id) { return { ...evt, date: data.data_evento ? data.data_evento.split("T")[0] : evt.date, time: data.horario || evt.time, name: data.observacoes_evento || evt.name, briefing: data.observacoes_evento || evt.briefing, fotId: data.fot_id || evt.fotId, empresaId: data.empresa_id || evt.empresaId, // If provided // Map team resource fields qtdFormandos: data.qtd_formandos ?? evt.qtdFormandos, qtdFotografos: data.qtd_fotografos ?? evt.qtdFotografos, qtdRecepcionistas: data.qtd_recepcionistas ?? evt.qtdRecepcionistas, qtdCinegrafistas: data.qtd_cinegrafistas ?? evt.qtdCinegrafistas, qtdEstudios: data.qtd_estudios ?? evt.qtdEstudios, qtdPontosFoto: data.qtd_pontos_foto ?? evt.qtdPontosFoto, qtdPontosDecorados: data.qtd_pontos_decorados ?? evt.qtdPontosDecorados, qtdPontosLed: data.qtd_pontos_led ?? evt.qtdPontosLed, qtdPlataforma360: data.qtd_plataforma_360 ?? evt.qtdPlataforma360, // Address is hard to parse back to object from payload without logic }; } return evt; })); } } catch (e) { console.error("Refresh failed", e); } }, functions, isLoading, refreshEvents, }} > {children} ); }; export const useData = () => { const context = useContext(DataContext); if (!context) throw new Error("useData must be used within a DataProvider"); return context; };