import React, { useState, useEffect, useMemo } from "react"; import { EventType, EventStatus, Address } from "../types"; import { Input, Select } from "./Input"; import { Button } from "./Button"; import { MapPin, Upload, Plus, X, Check, FileText, ExternalLink, Search, CheckCircle, Building2, AlertCircle, AlertTriangle, } from "lucide-react"; import { searchMapboxLocation, MapboxResult, reverseGeocode, } from "../services/mapboxService"; import { useAuth } from "../contexts/AuthContext"; import { useData } from "../contexts/DataContext"; import { UserRole } from "../types"; import { InstitutionForm } from "./InstitutionForm"; import { MapboxMap } from "./MapboxMap"; import { getEventTypes, EventTypeResponse, getCadastroFot, createAgenda, getCompanies } from "../services/apiService"; import { SearchableSelect, SearchableSelectOption } from "./SearchableSelect"; interface EventFormProps { onCancel: () => void; onSubmit: (data: any) => void; initialData?: any; } export const EventForm: React.FC = ({ onCancel, onSubmit, initialData, }) => { const { user, token: userToken } = useAuth(); const { addInstitution, } = useData(); const [activeTab, setActiveTab] = useState< "details" | "location" | "briefing" | "files" >("details"); const [addressQuery, setAddressQuery] = useState(""); const [addressResults, setAddressResults] = useState([]); const [isSearching, setIsSearching] = useState(false); const [isGeocoding, setIsGeocoding] = useState(false); const [showToast, setShowToast] = useState(false); const [showInstitutionForm, setShowInstitutionForm] = useState(false); const [eventTypes, setEventTypes] = useState([]); const [isBackendDown, setIsBackendDown] = useState(false); const [isLoadingEventTypes, setIsLoadingEventTypes] = useState(true); // Default State or Initial Data const [formData, setFormData] = useState( initialData || { name: "", date: "", time: "", startTime: "", endTime: "", type: "", status: EventStatus.PLANNING, locationName: "", // New field: Nome do Local address: { street: "", number: "", city: "", state: "", zip: "", lat: -22.7394, lng: -47.3314, mapLink: "", } as Address, briefing: "", contacts: [{ name: "", role: "", phone: "" }], files: [] as File[], institutionId: "", // Legacy, might clear or keep for compatibility attendees: "", courseId: "", // Legacy fotId: "", // New field for FOT linkage // Novos campos de gestão de equipe qtdFormandos: "", qtdFotografos: "", qtdRecepcionistas: "", qtdCinegrafistas: "", qtdEstudios: "", qtdPontosFoto: "", qtdPontosDecorados: "", qtdPontosLed: "", } ); const isClientRequest = user?.role === UserRole.EVENT_OWNER; const formTitle = initialData ? "Editar Evento" : isClientRequest ? "Solicitar Orçamento/Evento" : "Cadastrar Novo Evento"; const submitLabel = initialData ? "Salvar Alterações" : isClientRequest ? "Enviar Solicitação" : "Criar Evento"; // Fetch Event Types useEffect(() => { const fetchEventTypes = async () => { setIsLoadingEventTypes(true); const response = await getEventTypes(); if (response.isBackendDown) { setIsBackendDown(true); setEventTypes([]); } else if (response.data) { setIsBackendDown(false); setEventTypes(response.data); } setIsLoadingEventTypes(false); }; fetchEventTypes(); }, []); // Derived state for dropdowns const [companies, setCompanies] = useState([]); // New state for companies list const [selectedCompanyId, setSelectedCompanyId] = useState(""); // Changed from Name to ID const [selectedCourseName, setSelectedCourseName] = useState(""); const [selectedInstitutionName, setSelectedInstitutionName] = useState(""); // Load Companies for Business Owner / Superadmin useEffect(() => { if (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN) { const fetchCompanies = async () => { try { const response = await getCompanies(); if (response.data) { setCompanies(response.data); } } catch (error) { console.error("Failed to load companies", error); } }; fetchCompanies(); } }, [user]); // Populate form with initialData useEffect(() => { if (initialData) { console.log("EventForm received initialData:", initialData); // Robust mapping for API snake_case fields const mapLink = (initialData as any).local_evento || (initialData.address && initialData.address.mapLink) || ""; const mappedFotId = (initialData as any).fot_id || initialData.fotId || ""; const mappedEmpresaId = (initialData as any).empresa_id || (initialData as any).empresaId || ""; const mappedObservacoes = (initialData as any).observacoes_evento || initialData.name || ""; console.log("Mapped Values:", { mappedFotId, mappedEmpresaId, mappedObservacoes, mapLink }); // Parse Endereco String if Object is missing but String exists // Format expected: "Rua, Numero - Cidade/UF" or similar let addressData = initialData.address || { street: "", number: "", city: "", state: "", zip: "", lat: -22.7394, lng: -47.3314, mapLink: "" }; if (!initialData.address && (initialData as any).endereco) { // Simple heuristic parser or just default. User might need to refine. // Doing a best-effort copy to street if unstructured addressData = { ...addressData, street: (initialData as any).endereco || "" }; } // Safety: ensure all strings addressData = { street: addressData.street || "", number: addressData.number || "", city: addressData.city || "", state: addressData.state || "", zip: addressData.zip || "", lat: addressData.lat || -22.7394, lng: addressData.lng || -47.3314, mapLink: addressData.mapLink || "" }; // 1. Populate standard form fields setFormData(prev => ({ ...prev, ...initialData, startTime: initialData.time || (initialData as any).horario || "00:00", endTime: (initialData as any).horario_termino || prev.endTime || "", locationName: mapLink.includes('http') ? "" : mapLink, // Avoid putting URL in name fotId: mappedFotId, name: mappedObservacoes, // Map Observacoes to Name field (displayed as "Observacoes do Evento") briefing: mappedObservacoes, // Sync briefing address: addressData, // Mapear campos de gestão de equipe qtdFormandos: initialData.qtdFormandos || initialData.attendees || "", qtdFotografos: initialData.qtdFotografos || "", qtdRecepcionistas: initialData.qtdRecepcionistas || "", qtdCinegrafistas: initialData.qtdCinegrafistas || "", qtdEstudios: initialData.qtdEstudios || "", qtdPontosFoto: initialData.qtdPontosFoto || "", qtdPontosDecorados: initialData.qtdPontosDecorados || "", qtdPontosLed: initialData.qtdPontosLed || "", })); // 2. Populate derived dropdowns if data exists if (mappedEmpresaId && (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN)) { console.log("Setting Selected Company:", mappedEmpresaId); setSelectedCompanyId(mappedEmpresaId); } if (initialData.curso || (initialData as any).curso_nome) { setSelectedCourseName(initialData.curso || (initialData as any).curso_nome || ""); } if (initialData.instituicao) { setSelectedInstitutionName(initialData.instituicao || ""); } } }, [initialData, user?.role]); // Fetch FOTs filtered by user company OR selected company const [availableFots, setAvailableFots] = useState([]); const [loadingFots, setLoadingFots] = useState(false); useEffect(() => { const loadFots = async () => { // Determine which company ID to use let targetEmpresaId = user?.empresaId; if (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN) { // Must select a company first if (!selectedCompanyId) { setAvailableFots([]); return; } targetEmpresaId = selectedCompanyId; } // If we have a target company (or user is linked), fetch FOTs if (targetEmpresaId || user?.role === UserRole.EVENT_OWNER) { setLoadingFots(true); // Clear previous FOTs to force UI update and avoid stale data setAvailableFots([]); console.log("Fetching FOTs for company:", targetEmpresaId); const token = localStorage.getItem("token") || ""; const response = await getCadastroFot(token, targetEmpresaId); if (response.data) { console.log("FOTs loaded:", response.data.length); console.log("FOT Sample:", response.data[0]); console.log("Finalized FOTs:", response.data.filter(f => f.finalizada)); setAvailableFots(response.data); } setLoadingFots(false); } }; loadFots(); }, [user, selectedCompanyId]); // Unique Courses (from availableFots - which are already specific to the company) const uniqueCourses = Array.from(new Set(availableFots.map(f => f.curso_nome))).sort(); // Filtered Institutions based on Course const filteredInstitutions = availableFots .filter(f => f.curso_nome === selectedCourseName) .map(f => f.instituicao) .filter((v, i, a) => a.indexOf(v) === i) .sort(); // Filtered Years based on Course + Inst const filteredYears = availableFots .filter(f => f.curso_nome === selectedCourseName && f.instituicao === selectedInstitutionName) .map(f => ({ id: f.id, label: f.ano_formatura_label })); // Address Autocomplete Logic using Mapbox useEffect(() => { const timer = setTimeout(async () => { if (addressQuery.length > 3) { setIsSearching(true); const results = await searchMapboxLocation(addressQuery); setAddressResults(results); setIsSearching(false); } else { setAddressResults([]); } }, 500); return () => clearTimeout(timer); }, [addressQuery]); const handleAddressSelect = (addr: MapboxResult) => { setFormData((prev: any) => ({ ...prev, address: { street: addr.street, number: addr.number, city: addr.city, state: addr.state, zip: addr.zip, lat: addr.lat, lng: addr.lng, mapLink: addr.mapLink, }, })); setAddressQuery(""); setAddressResults([]); }; const handleMapLocationChange = async (lat: number, lng: number) => { const addressData = await reverseGeocode(lat, lng); if (addressData) { setFormData((prev: any) => ({ ...prev, address: { street: addressData.street, number: addressData.number, city: addressData.city, state: addressData.state, zip: addressData.zip, lat: addressData.lat, lng: addressData.lng, mapLink: addressData.mapLink, }, })); } else { setFormData((prev: any) => ({ ...prev, address: { ...prev.address, lat, lng, mapLink: `https://www.google.com/maps/search/?api=1&query=${lat},${lng}`, }, })); } }; const handleManualAddressChange = async () => { const { street, number, city, state } = formData.address; const query = `${street} ${number}, ${city}, ${state}`.trim(); if (query.length < 5) return; setIsGeocoding(true); try { const results = await searchMapboxLocation(query); if (results.length > 0) { const firstResult = results[0]; setFormData((prev: any) => ({ ...prev, address: { ...prev.address, lat: firstResult.lat, lng: firstResult.lng, mapLink: firstResult.mapLink, }, })); } } catch (error) { console.error("Erro ao geocodificar endereço manual:", error); } finally { setIsGeocoding(false); } }; const addContact = () => { setFormData((prev: any) => ({ ...prev, contacts: [...prev.contacts, { name: "", role: "", phone: "" }], })); }; const removeContact = (index: number) => { const newContacts = [...formData.contacts]; newContacts.splice(index, 1); setFormData((prev: any) => ({ ...prev, contacts: newContacts })); }; const handleFileUpload = (e: React.ChangeEvent) => { if (e.target.files) { setFormData((prev: any) => ({ ...prev, files: [...prev.files, ...Array.from(e.target.files || [])], })); } }; const handleSubmit = async () => { // Validation (Observations are optional) // if (!formData.name) return alert("Preencha o tipo de evento"); if (!formData.date) return alert("Preencha a data"); if (!formData.attendees || parseInt(formData.attendees) <= 0) return alert("Preencha o número de formandos"); // Validação condicional apenas para empresas if ((user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN)) { if (!formData.qtdFotografos || parseInt(formData.qtdFotografos) <= 0) { return alert("Preencha a quantidade de fotógrafos necessários"); } } // Ensure typeId is valid let finalTypeId = formData.typeId; if (!finalTypeId || finalTypeId === "00000000-0000-0000-0000-000000000000") { // Try to match by name const matchingType = eventTypes.find(t => t.nome === formData.type); if (matchingType) { finalTypeId = matchingType.id; } else { // If strictly required by DB, we must stop. // But for legacy compatibility, maybe we prompt user return alert("Tipo de evento inválido. Por favor, selecione novamente o tipo do evento."); } } if (!formData.fotId) { alert("ERRO CRÍTICO: Turma (FOT) não identificada. Por favor, selecione a Turma ou Empresa novamente."); return; } try { // Prepare Payload for Agenda API const payload = { fot_id: formData.fotId, // Must be valid UUID tipo_evento_id: finalTypeId, data_evento: new Date(formData.date).toISOString(), horario: formData.startTime || "", horario_fim: formData.endTime || "", observacoes_evento: formData.name || formData.briefing || "", local_evento: formData.locationName || "", endereco: `${formData.address.street}, ${formData.address.number} - ${formData.address.city}/${formData.address.state}`, qtd_formandos: parseInt(formData.attendees) || 0, // Campos de gestão de equipe qtd_fotografos: parseInt(formData.qtdFotografos) || 0, qtd_recepcionistas: parseInt(formData.qtdRecepcionistas) || 0, qtd_cinegrafistas: parseInt(formData.qtdCinegrafistas) || 0, qtd_estudios: parseInt(formData.qtdEstudios) || 0, qtd_pontos_foto: parseInt(formData.qtdPontosFoto) || 0, qtd_pontos_decorados: parseInt(formData.qtdPontosDecorados) || 0, qtd_pontos_led: parseInt(formData.qtdPontosLed) || 0, status_profissionais: "PENDING", cine_faltante: 0, logistica_observacoes: "", pre_venda: true, contatos: formData.contacts }; // Submit to parent handler if (onSubmit) { await onSubmit(payload); } setShowToast(true); // alert("Solicitação enviada com sucesso!"); // O toast já é suficiente e mais elegante } catch (e: any) { console.error(e); alert("Erro ao salvar: " + (e.message || "Erro desconhecido")); setShowToast(false); } }; const handleInstitutionSubmit = (institutionData: any) => { const newInstitution = { ...institutionData, id: `inst-${Date.now()}`, ownerId: user?.id || "", }; addInstitution(newInstitution); setFormData((prev: any) => ({ ...prev, institutionId: newInstitution.id })); setShowInstitutionForm(false); }; if (showInstitutionForm) { return (
setShowInstitutionForm(false)} onSubmit={handleInstitutionSubmit} userId={user?.id || ""} />
); } return (
{/* Success Toast */} {showToast && (

Sucesso!

As informações foram salvas.

)} {/* Form Header */}

{formTitle}

{isClientRequest ? "Preencha os detalhes do seu sonho. Nossa equipe analisará em breve." : "Preencha as informações técnicas do evento."}

{/* Step indicators */}
{["details", "location", "briefing", "files"].map((tab, idx) => (
{idx + 1}
))}
{/* Mobile Tabs */}
{[ { id: "details", label: "Detalhes", icon: "1" }, { id: "location", label: "Localização", icon: "2" }, { id: "briefing", label: isClientRequest ? "Desejos" : "Briefing", icon: "3", }, { id: "files", label: "Arquivos", icon: "4" }, ].map((item) => ( ))}
{/* Desktop Sidebar Navigation */}
{[ { id: "details", label: "Detalhes & Data" }, { id: "location", label: "Localização" }, { id: "briefing", label: isClientRequest ? "Seus Desejos" : "Briefing & Equipe", }, { id: "files", label: "Inspirações" }, ].map((item) => ( ))}
{/* Form Content */}
{activeTab === "details" && (
{isBackendDown && (
Backend não está rodando. Não é possível carregar os tipos de eventos.
)} {isLoadingEventTypes && !isBackendDown && (
Carregando tipos de eventos...
)}
setFormData({ ...formData, name: e.target.value }) } /> setFormData({ ...formData, date: e.target.value }) } />
setFormData({ ...formData, startTime: e.target.value }) } required /> setFormData({ ...formData, endTime: e.target.value }) } />
{ const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, attendees: value, qtdFormandos: value }); } }} type="text" inputMode="numeric" required /> {/* Seção de Gestão de Equipe - Apenas para Empresas */} {(user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN) && (

Gestão de Equipe e Recursos

{ const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, qtdFotografos: value }); } }} type="text" inputMode="numeric" required /> { const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, qtdRecepcionistas: value }); } }} type="text" inputMode="numeric" /> { const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, qtdCinegrafistas: value }); } }} type="text" inputMode="numeric" /> { const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, qtdEstudios: value }); } }} type="text" inputMode="numeric" /> { const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, qtdPontosFoto: value }); } }} type="text" inputMode="numeric" /> { const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, qtdPontosDecorados: value }); } }} type="text" inputMode="numeric" /> { const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setFormData({ ...formData, qtdPontosLed: value }); } }} type="text" inputMode="numeric" />
)} {/* Dynamic FOT Selection */}

Seleção da Turma

{!user?.empresaId && user?.role !== UserRole.SUPERADMIN && user?.role !== UserRole.BUSINESS_OWNER ? (

Sua conta não está vinculada a nenhuma empresa. Por favor, entre em contato com a administração para regularizar seu cadastro antes de solicitar um evento.

) : ( <> {/* 0. Empresa (Only for Business Owner / Superadmin) */} {(user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN) && (
)} {/* Warning if Company Selected but No FOTs */} {selectedCompanyId && !loadingFots && availableFots.length === 0 && (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN) && (

Esta empresa selecionada ainda não possui turmas, cursos ou FOTs cadastrados.

)} {/* Consolidated Turma Selection */}
{ const selectedFot = availableFots.find(f => f.id === value); if (selectedFot) { setFormData({ ...formData, fotId: value, }); } else { setFormData({ ...formData, fotId: "" }); } }} disabled={loadingFots || ((user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN) && !selectedCompanyId)} options={availableFots.map(f => ({ value: f.id, label: `${f.curso_nome} - ${f.instituicao} - ${f.ano_formatura_label} (FOT ${f.fot})${f.finalizada ? " (FINALIZADA)" : ""}`, disabled: f.finalizada, className: f.finalizada ? "text-red-600 bg-red-50 font-bold" : "", style: f.finalizada ? { color: 'red', fontWeight: 'bold' } : undefined }))} /> {loadingFots &&

Carregando turmas...

}
)}
)} {activeTab === "location" && (
{/* Nome do Local */} setFormData({ ...formData, locationName: e.target.value })} />
setAddressQuery(e.target.value)} />
{isSearching ? (
) : ( )}
{addressResults.length > 0 && (
    {addressResults.map((addr, idx) => (
  • handleAddressSelect(addr)} >

    {addr.description}

    {addr.city}, {addr.state}

    {addr.mapLink && ( Maps Maps )}
  • ))}
)}
{ setFormData({ ...formData, address: { ...formData.address, street: e.target.value, }, }); }} onBlur={handleManualAddressChange} placeholder="Digite o nome da rua" />
{ const value = e.target.value; setFormData({ ...formData, address: { ...formData.address, number: value }, }); }} onBlur={handleManualAddressChange} type="text" inputMode="numeric" />
{ setFormData({ ...formData, address: { ...formData.address, city: e.target.value }, }); }} onBlur={handleManualAddressChange} placeholder="Digite a cidade" /> { const value = e.target.value.toUpperCase().slice(0, 2); setFormData({ ...formData, address: { ...formData.address, state: value }, }); }} onBlur={handleManualAddressChange} placeholder="SP" maxLength={2} />
{/* Mapa Interativo */}
{formData.address.mapLink && (
Localização verificada via Mapbox Ver no mapa
)}
)} {activeTab === "briefing" && (
{formData.contacts.map((contact: any, idx: number) => (
{ const newContacts = [...formData.contacts]; newContacts[idx].name = e.target.value; setFormData({ ...formData, contacts: newContacts }); }} /> { const newContacts = [...formData.contacts]; newContacts[idx].role = e.target.value; setFormData({ ...formData, contacts: newContacts }); }} /> { const newContacts = [...formData.contacts]; newContacts[idx].phone = e.target.value; setFormData({ ...formData, contacts: newContacts }); }} />
))}
)} {activeTab === "files" && (

{isClientRequest ? "Anexe referências visuais (Moodboard)" : "Anexe contratos e cronogramas"}

PDF, JPG, PNG (Max 10MB)

{(formData.files || []).length > 0 && (

Arquivos Selecionados:

{formData.files.map((file: any, idx: number) => (

{file.name}

{(file.size / 1024).toFixed(1)} KB

))}
)}
)}
); };