import React, { useState, useEffect } from "react"; import { Plus, Trash, User, Truck, MapPin } from "lucide-react"; import { useAuth } from "../contexts/AuthContext"; import { listEscalas, createEscala, deleteEscala, EscalaInput, getFunctions } from "../services/apiService"; import { useData } from "../contexts/DataContext"; import { UserRole } from "../types"; interface EventSchedulerProps { agendaId: string; dataEvento: string; // YYYY-MM-DD allowedProfessionals?: { professional_id?: string; professionalId?: string; status?: string }[] | string[]; // IDs or Objects onUpdateStats?: (stats: { studios: number }) => void; defaultTime?: string; } const timeSlots = [ "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "00:00" ]; const EventScheduler: React.FC = ({ agendaId, dataEvento, allowedProfessionals, onUpdateStats, defaultTime }) => { const { token, user } = useAuth(); const { professionals, events, functions } = useData(); const [escalas, setEscalas] = useState([]); const roles = functions; const [loading, setLoading] = useState(false); // New entry state const [selectedProf, setSelectedProf] = useState(""); const [startTime, setStartTime] = useState(defaultTime || "08:00"); const [endTime, setEndTime] = useState("12:00"); // Could calculated based on start, but keep simple const [role, setRole] = useState(""); const isEditable = user?.role === UserRole.SUPERADMIN || user?.role === UserRole.BUSINESS_OWNER; const canViewSchedule = user?.role !== UserRole.EVENT_OWNER; // EVENT_OWNER can't see schedule details // Helper to check availability const checkAvailability = (profId: string) => { // Parse current request time in minutes const [h, m] = startTime.split(':').map(Number); const reqStart = h * 60 + m; const reqEnd = reqStart + 240; // +4 hours // Check if professional is in any other event on the same day with overlap const conflict = events.some(e => { if (e.id === agendaId) return false; // Ignore current event (allow re-scheduling or moving within same event?) // Actually usually we don't want to double book in same event either unless intention is specific. // But 'escalas' check (Line 115) already handles "already in this scale". // If they are assigned to the *Event Team* (Logistics) but not Scale yet, it doesn't mean they are busy for THIS exact time? // Wait, 'events.photographerIds' means they are on the Team. // Being on the Team uses generic time? // For now, assume busy if in another event team. // Fix for Request 2: Only consider BUSY if status is 'Confirmado' in the other event? // The frontend 'events' list might generally show all events. // But 'events' from 'useData' implies basic info. // If we access specific assigned status here, we could filter. // The `events` array usually has basic info. If `assigned_professionals` is detailed there, we could check status. // Assuming `e.photographerIds` is just IDs. // We'll leave backend to strictly enforce, but frontend hint is good. // Check if professional is in any other event on the same day with overlap // FIX: Only consider BUSY if status is 'ACEITO' (Confirmed) const isAssignedConfirmed = (e.assignments || []).some(a => a.professionalId === profId && a.status === 'ACEITO'); if (e.date === dataEvento && isAssignedConfirmed) { const [eh, em] = (e.time || "00:00").split(':').map(Number); const evtStart = eh * 60 + em; const evtEnd = evtStart + 240; // Assume 4h duration for other events too // Overlap check return (reqStart < evtEnd && evtStart < reqEnd); } return false; }); return conflict; }; useEffect(() => { if (agendaId && token) { fetchEscalas(); // Functions handled via context } }, [agendaId, token]); // Recalculate stats whenever scales change useEffect(() => { if (onUpdateStats && escalas.length >= 0) { let totalStudios = 0; escalas.forEach(item => { const prof = professionals.find(p => p.id === item.profissional_id); if (prof && prof.qtd_estudio) { totalStudios += prof.qtd_estudio; } }); onUpdateStats({ studios: totalStudios }); } }, [escalas, professionals, onUpdateStats]); const fetchEscalas = async () => { setLoading(true); const result = await listEscalas(agendaId, token!); if (result.data) { setEscalas(result.data); } setLoading(false); }; const handleAddEscala = async () => { if (!selectedProf || !startTime) return; // Create Date objects from Local Time const localStart = new Date(`${dataEvento}T${startTime}:00`); const localEnd = new Date(localStart); localEnd.setHours(localEnd.getHours() + 4); // Convert to UTC ISO String and format for backend (Space, no ms) // toISOString returns YYYY-MM-DDTHH:mm:ss.sssZ const startISO = localStart.toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, 'Z'); const endISO = localEnd.toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, 'Z'); const input: EscalaInput = { agenda_id: agendaId, profissional_id: selectedProf, data_hora_inicio: startISO, data_hora_fim: endISO, funcao_especifica: role }; const res = await createEscala(input, token!); if (res.data) { fetchEscalas(); setSelectedProf(""); setRole(""); } else { alert("Erro ao criar escala: " + res.error); } }; const handleDelete = async (id: string) => { if (confirm("Remover profissional da escala?")) { await deleteEscala(id, token!); fetchEscalas(); } }; // 1. Start with all professionals or just the allowed ones // FILTER: Only show professionals with a valid role (Function), matching "Equipe" page logic. let availableProfs = professionals.filter(p => roles.some(r => r.id === p.funcao_profissional_id)); const allowedMap = new Map(); // ID -> Status const assignedRoleMap = new Map(); // ID -> Role Name if (allowedProfessionals) { // Normalize allowed list const ids: string[] = []; allowedProfessionals.forEach((p: any) => { if (typeof p === 'string') { ids.push(p); allowedMap.set(p, 'Confirmado'); // Default if not detailed } else { const pid = p.professional_id || p.professionalId; const status = p.status || 'Pendente'; // Filter out Rejected professionals from the available list if (pid && status !== 'REJEITADO' && status !== 'Rejeitado') { ids.push(pid); allowedMap.set(pid, status); // Use assigned role ID (handle both casing) const fId = p.funcaoId || p.funcao_id; if (fId) { const r = roles.find(role => role.id === fId); if (r) assignedRoleMap.set(pid, r.nome); } } } }); availableProfs = availableProfs.filter(p => ids.includes(p.id)); } // 2. Filter out professionals already in schedule to prevent duplicates // But keep the currently selected one valid if it was just selected availableProfs = availableProfs.filter(p => !escalas.some(e => e.profissional_id === p.id)); const selectedProfessionalData = professionals.find(p => p.id === selectedProf); return (

Escala de Profissionais

{/* Warning if restricting and empty */} {isEditable && allowedProfessionals && allowedProfessionals.length === 0 && (
Nenhum profissional atribuído a este evento. Adicione membros à equipe antes de criar a escala.
)} {/* Add Form - Only for Admins */} {isEditable && (
setStartTime(e.target.value)} />
setRole(e.target.value)} />
{/* Equipment Info Preview */} {selectedProfessionalData && (selectedProfessionalData.equipamentos || selectedProfessionalData.qtd_estudio > 0) && (
Equipamentos: {selectedProfessionalData.equipamentos || "Nenhum cadastrado"} {selectedProfessionalData.qtd_estudio > 0 && ( • Possui {selectedProfessionalData.qtd_estudio} Estúdio(s) )}
)}
)} {/* Timeline / List */}
{loading ?

Carregando...

: escalas.length === 0 ? (

Nenhuma escala definida.

) : ( escalas.map(item => { // Find professional data again to show equipment in list if needed // Ideally backend should return it, but for now we look up in global list if available const profData = professionals.find(p => p.id === item.profissional_id); const assignedRole = assignedRoleMap.get(item.profissional_id); // Resolve role name const defaultProfRole = profData ? roles.find(r => r.id === profData.funcao_profissional_id)?.nome : undefined; const displayRole = assignedRole || item.profissional_role || defaultProfRole || (profData as any)?.funcao_nome || profData?.role; return (
{item.avatar_url ? ( ) : ( )}

{item.profissional_nome} {displayRole && ({displayRole})} {item.phone && ({item.phone})}

{new Date(item.start).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - {new Date(item.end).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} {item.role && {item.role}}

{isEditable && ( )}
{/* Show equipment if available */} {profData && profData.equipamentos && (
Equip: {profData.equipamentos}
)}
); }) )}
); }; export default EventScheduler;