diff --git a/frontend/components/EventForm.tsx b/frontend/components/EventForm.tsx index 5e9fb0e..1601b15 100644 --- a/frontend/components/EventForm.tsx +++ b/frontend/components/EventForm.tsx @@ -85,6 +85,16 @@ export const EventForm: React.FC = ({ attendees: "", courseId: "", // Legacy fotId: "", // New field for FOT linkage + + // Novos campos de gestão de equipe + qtdFormandos: "", + qtdFotografos: "", + qtdRecepcionistas: "", + qtdCinegrafistas: "", + qtdEstudios: "", + qtdPontosFoto: "", + qtdPontosDecorados: "", + qtdPontosLed: "", } ); @@ -193,6 +203,16 @@ export const EventForm: React.FC = ({ 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 @@ -381,6 +401,14 @@ export const EventForm: React.FC = ({ // Validation 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; @@ -415,20 +443,16 @@ export const EventForm: React.FC = ({ endereco: `${formData.address.street}, ${formData.address.number} - ${formData.address.city}/${formData.address.state}`, qtd_formandos: parseInt(formData.attendees) || 0, - // Default integer values - 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, + // 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", - foto_faltante: 0, - recep_faltante: 0, cine_faltante: 0, logistica_observacoes: "", pre_venda: true @@ -658,19 +682,121 @@ export const EventForm: React.FC = ({ { const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { - setFormData({ ...formData, attendees: 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

@@ -916,7 +1042,7 @@ export const EventForm: React.FC = ({ {/* Mapa Interativo */}
)} )} diff --git a/frontend/components/MapboxMap.tsx b/frontend/components/MapboxMap.tsx index c8af1cc..a169c9d 100644 --- a/frontend/components/MapboxMap.tsx +++ b/frontend/components/MapboxMap.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; import mapboxgl from "mapbox-gl"; import "mapbox-gl/dist/mapbox-gl.css"; -import { MapPin, Target } from "lucide-react"; +import { MapPin, Target, Lightbulb } from "lucide-react"; interface MapboxMapProps { initialLat?: number; @@ -185,8 +185,8 @@ export const MapboxMap: React.FC = ({ {/* Card de Instruções */}
-

- 💡 Como usar: +

+ Como usar:

  • diff --git a/frontend/components/ProfessionalForm.tsx b/frontend/components/ProfessionalForm.tsx index 55e211b..f7fb2fe 100644 --- a/frontend/components/ProfessionalForm.tsx +++ b/frontend/components/ProfessionalForm.tsx @@ -497,14 +497,16 @@ export const ProfessionalForm: React.FC = ({
    handleChange("banco", e.target.value)} /> { const value = e.target.value.replace(/\D/g, ""); @@ -515,8 +517,9 @@ export const ProfessionalForm: React.FC = ({
    handleChange("conta", e.target.value)} /> diff --git a/frontend/contexts/DataContext.tsx b/frontend/contexts/DataContext.tsx index 229e790..90d55ce 100644 --- a/frontend/contexts/DataContext.tsx +++ b/frontend/contexts/DataContext.tsx @@ -952,7 +952,14 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ const professional = professionals.find((p) => p.usuarioId === userId); if (!professional) return []; const professionalId = professional.id; - return events.filter((e) => e.photographerIds.includes(professionalId)); + 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 []; }; diff --git a/frontend/pages/CourseManagement.tsx b/frontend/pages/CourseManagement.tsx index 056bbb5..ee3afa0 100644 --- a/frontend/pages/CourseManagement.tsx +++ b/frontend/pages/CourseManagement.tsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from "react"; import { useAuth } from "../contexts/AuthContext"; +import { useData } from "../contexts/DataContext"; import { UserRole } from "../types"; import { Button } from "../components/Button"; -import { getCadastroFot, deleteCadastroFot } from "../services/apiService"; +import { getCadastroFot, deleteCadastroFot, checkFotHasEvents } from "../services/apiService"; import { Briefcase, AlertTriangle, Plus, Edit, Trash2, Search, Filter } from "lucide-react"; import { FotForm } from "../components/FotForm"; @@ -25,6 +26,7 @@ interface FotData { export const CourseManagement: React.FC = () => { const { user } = useAuth(); + const { events } = useData(); const [fotList, setFotList] = useState([]); const [filteredList, setFilteredList] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -96,12 +98,42 @@ export const CourseManagement: React.FC = () => { }; const handleDelete = async (id: string, fotNumber: number) => { - if (!window.confirm(`Tem certeza que deseja excluir o FOT ${fotNumber}?`)) return; - const token = localStorage.getItem("token"); if (!token) return; try { + // Primeiro, verificar se há eventos associados ao FOT + // Verificação local usando os eventos do DataContext + const associatedEvents = events.filter(event => + event.fotId === id || + (typeof event.fot === 'number' && event.fot === fotNumber) + ); + + if (associatedEvents.length > 0) { + alert( + `Não é possível excluir este FOT pois existem ${associatedEvents.length} evento(s) associado(s) a ele.\n\n` + + `Eventos associados:\n${associatedEvents.map(e => `- ${e.name} (${new Date(e.date + "T00:00:00").toLocaleDateString("pt-BR")})`).join('\n')}` + ); + return; + } + + // Tentar verificação adicional via API se disponível + try { + const checkResult = await checkFotHasEvents(id, token); + if (checkResult.data?.hasEvents) { + alert( + `Não é possível excluir este FOT pois existem ${checkResult.data.eventCount} evento(s) associado(s) a ele no sistema.` + ); + return; + } + } catch (apiError) { + // Se a API não estiver disponível, continua com a verificação local + console.log("API check not available, using local verification"); + } + + // Se chegou até aqui, não há eventos associados + if (!window.confirm(`Tem certeza que deseja excluir o FOT ${fotNumber}?`)) return; + const res = await deleteCadastroFot(id, token); if (res.error) { alert(res.error); @@ -325,8 +357,21 @@ export const CourseManagement: React.FC = () => { className="hover:bg-gray-50 transition-colors" > -
    - {item.fot || "-"} +
    +
    + {item.fot || "-"} +
    + {(() => { + const hasEvents = events.some(event => + event.fotId === item.id || + (typeof event.fot === 'number' && event.fot === item.fot) + ); + return hasEvents && ( + + Com eventos + + ); + })()}
    @@ -393,13 +438,26 @@ export const CourseManagement: React.FC = () => { > - + {(() => { + const hasEvents = events.some(event => + event.fotId === item.id || + (typeof event.fot === 'number' && event.fot === item.fot) + ); + return ( + + ); + })()}
    diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index d2a00a0..b72dcc9 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -100,6 +100,12 @@ export const Dashboard: React.FC = ({ }); const [isTeamModalOpen, setIsTeamModalOpen] = useState(false); const [viewingProfessional, setViewingProfessional] = useState(null); + + // Estados para filtros do modal de equipe + const [teamSearchTerm, setTeamSearchTerm] = useState(""); + const [teamRoleFilter, setTeamRoleFilter] = useState("all"); + const [teamStatusFilter, setTeamStatusFilter] = useState("all"); + const [teamAvailabilityFilter, setTeamAvailabilityFilter] = useState("all"); useEffect(() => { if (initialView) { @@ -112,6 +118,129 @@ export const Dashboard: React.FC = ({ setViewingProfessional(professional); }; + // Função para calcular profissionais faltantes e status + const calculateTeamStatus = (event: EventData) => { + const assignments = event.assignments || []; + + // Contadores de profissionais aceitos por tipo + const acceptedFotografos = assignments.filter(a => { + if (a.status !== "ACEITO") return false; + const professional = professionals.find(p => p.id === a.professionalId); + return professional && (professional.role || "").toLowerCase().includes("fot"); + }).length; + + const acceptedRecepcionistas = assignments.filter(a => { + if (a.status !== "ACEITO") return false; + const professional = professionals.find(p => p.id === a.professionalId); + return professional && (professional.role || "").toLowerCase().includes("recep"); + }).length; + + const acceptedCinegrafistas = assignments.filter(a => { + if (a.status !== "ACEITO") return false; + const professional = professionals.find(p => p.id === a.professionalId); + return professional && (professional.role || "").toLowerCase().includes("cine"); + }).length; + + // Quantidades necessárias + const qtdFotografos = event.qtdFotografos || 0; + const qtdRecepcionistas = event.qtdRecepcionistas || 0; + const qtdCinegrafistas = event.qtdCinegrafistas || 0; + + // Calcular faltantes + const fotoFaltante = Math.max(0, qtdFotografos - acceptedFotografos); + const recepFaltante = Math.max(0, qtdRecepcionistas - acceptedRecepcionistas); + const cineFaltante = Math.max(0, qtdCinegrafistas - acceptedCinegrafistas); + + // Verificar se todos os profissionais estão OK + const profissionaisOK = fotoFaltante === 0 && recepFaltante === 0 && cineFaltante === 0; + + return { + acceptedFotografos, + acceptedRecepcionistas, + acceptedCinegrafistas, + fotoFaltante, + recepFaltante, + cineFaltante, + profissionaisOK + }; + }; + + // Função para fechar modal de equipe e limpar filtros + const closeTeamModal = () => { + setIsTeamModalOpen(false); + setTeamSearchTerm(""); + setTeamRoleFilter("all"); + setTeamStatusFilter("all"); + setTeamAvailabilityFilter("all"); + }; + + // Função para filtrar profissionais no modal de equipe + const getFilteredTeamProfessionals = () => { + if (!selectedEvent) return professionals; + + return professionals.filter((professional) => { + // Filtro por busca (nome ou email) + if (teamSearchTerm) { + const searchLower = teamSearchTerm.toLowerCase(); + const nameMatch = (professional.name || professional.nome || "").toLowerCase().includes(searchLower); + const emailMatch = (professional.email || "").toLowerCase().includes(searchLower); + if (!nameMatch && !emailMatch) return false; + } + + // Filtro por função/role + if (teamRoleFilter !== "all") { + const professionalRole = (professional.role || "").toLowerCase(); + if (!professionalRole.includes(teamRoleFilter)) return false; + } + + // Verificar status do assignment para este evento + const assignment = (selectedEvent.assignments || []).find( + (a) => a.professionalId === professional.id + ); + const status = assignment ? assignment.status : null; + const isAssigned = !!status && status !== "REJEITADO"; + + // Verificar se está ocupado em outro evento na mesma data + const isBusy = !isAssigned && events.some(e => + e.id !== selectedEvent.id && + e.date === selectedEvent.date && + (e.assignments || []).some(a => a.professionalId === professional.id && a.status === 'ACEITO') + ); + + // Filtro por status + if (teamStatusFilter !== "all") { + switch (teamStatusFilter) { + case "assigned": + if (!isAssigned) return false; + break; + case "available": + if (isAssigned || isBusy) return false; + break; + case "busy": + if (!isBusy) return false; + break; + case "rejected": + if (!status || status !== "REJEITADO") return false; + break; + } + } + + // Filtro por disponibilidade + if (teamAvailabilityFilter !== "all") { + switch (teamAvailabilityFilter) { + case "available": + if (isAssigned || isBusy) return false; + break; + case "unavailable": + if (!isAssigned && !isBusy) return false; + break; + } + } + + return true; + }); + }; + // Guard Clause for basic security if (!user) return
    Acesso Negado. Faça login.
    ; @@ -175,6 +304,11 @@ export const Dashboard: React.FC = ({ updateEventStatus(eventId, EventStatus.CONFIRMED); }; + const handleReject = (e: React.MouseEvent, eventId: string, reason?: string) => { + e.stopPropagation(); + updateEventStatus(eventId, EventStatus.ARCHIVED, reason); + }; + const handleAssignmentResponse = async ( e: React.MouseEvent, eventId: string, @@ -344,9 +478,12 @@ export const Dashboard: React.FC = ({ setView("details"); }} onApprove={handleApprove} + onReject={handleReject} userRole={user.role} currentProfessionalId={currentProfessionalId} onAssignmentResponse={handleAssignmentResponse} + isManagingTeam={false} // Na gestão geral, não está gerenciando equipe + professionals={professionals} // Adicionar lista de profissionais />
    )} @@ -607,6 +744,141 @@ export const Dashboard: React.FC = ({ {selectedEvent.address.zip && ` | CEP: ${selectedEvent.address.zip}`} + + {/* Seção de Gestão de Equipe */} + + + Gestão de Equipe e Recursos + + + + + QTD Formandos + + + {selectedEvent.qtdFormandos || selectedEvent.attendees || "-"} + + + + + Fotógrafo + + + {selectedEvent.qtdFotografos || "-"} + + + + + Recepcionista + + + {selectedEvent.qtdRecepcionistas || "-"} + + + + + Cinegrafista + + + {selectedEvent.qtdCinegrafistas || "-"} + + + + + Estúdio + + + {selectedEvent.qtdEstudios || "-"} + + + + + Ponto de Foto + + + {selectedEvent.qtdPontosFoto || "-"} + + + + + Ponto Decorado + + + {selectedEvent.qtdPontosDecorados || "-"} + + + + + Ponto LED + + + {selectedEvent.qtdPontosLed || "-"} + + + + {/* Status e Faltantes */} + {(() => { + const teamStatus = calculateTeamStatus(selectedEvent); + return ( + <> + + + Profissionais OK? + + + + {teamStatus.profissionaisOK ? ( + <> + + Completo + + ) : ( + <> + + Incompleto + + )} + + + + + + Foto Faltante + + + 0 ? "text-red-600" : "text-green-600"}`}> + {teamStatus.fotoFaltante} + + + + + + Recep. Faltante + + + 0 ? "text-red-600" : "text-green-600"}`}> + {teamStatus.recepFaltante} + + + + + + Cine Faltante + + + 0 ? "text-red-600" : "text-green-600"}`}> + {teamStatus.cineFaltante} + + + + + ); + })()} + Horário @@ -874,7 +1146,7 @@ export const Dashboard: React.FC = ({

+ {/* Filtros e Busca */} +
+ {/* Barra de busca */} +
+ + setTeamSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-purple focus:border-transparent" + /> +
+ + {/* Filtros */} +
+ {/* Filtro por função */} + + + {/* Filtro por status */} + + + {/* Filtro por disponibilidade */} + + + {/* Botão limpar filtros */} + {(teamSearchTerm || teamRoleFilter !== "all" || teamStatusFilter !== "all" || teamAvailabilityFilter !== "all") && ( + + )} +
+
+ {/* Tabela de Profissionais (Desktop) */}
@@ -918,7 +1261,7 @@ export const Dashboard: React.FC = ({ - {professionals.map((photographer) => { + {getFilteredTeamProfessionals().map((photographer) => { const assignment = (selectedEvent.assignments || []).find( (a) => a.professionalId === photographer.id ); @@ -986,7 +1329,10 @@ export const Dashboard: React.FC = ({ )} {status === "REJEITADO" && ( - + Recusado @@ -1017,17 +1363,22 @@ export const Dashboard: React.FC = ({ ); })} - {professionals.length === 0 && ( + {getFilteredTeamProfessionals().length === 0 && ( @@ -1039,7 +1390,7 @@ export const Dashboard: React.FC = ({ {/* Lista de Cards (Mobile) */}
- {professionals.map((photographer) => { + {getFilteredTeamProfessionals().map((photographer) => { const assignment = (selectedEvent.assignments || []).find( (a) => a.professionalId === photographer.id ); @@ -1084,7 +1435,10 @@ export const Dashboard: React.FC = ({ )} {status === "REJEITADO" && ( - + Recusado )} @@ -1131,7 +1485,7 @@ export const Dashboard: React.FC = ({
diff --git a/frontend/pages/Team.tsx b/frontend/pages/Team.tsx index 77b1f2b..6a62157 100644 --- a/frontend/pages/Team.tsx +++ b/frontend/pages/Team.tsx @@ -57,6 +57,7 @@ export const TeamPage: React.FC = () => { const [searchTerm, setSearchTerm] = useState(""); const [roleFilter, setRoleFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all"); + const [ratingFilter, setRatingFilter] = useState("all"); // Selection & Modals const [selectedProfessional, setSelectedProfessional] = useState(null); @@ -419,10 +420,26 @@ export const TeamPage: React.FC = () => { const roleName = getRoleName(p.funcao_profissional_id); const matchesRole = roleFilter === "all" || roleName === roleFilter; + // Rating filter logic + const matchesRating = (() => { + if (ratingFilter === "all") return true; + const rating = p.media || 0; + + switch (ratingFilter) { + case "5": return rating >= 4.5; + case "4": return rating >= 4 && rating < 4.5; + case "3": return rating >= 3 && rating < 4; + case "2": return rating >= 2 && rating < 3; + case "1": return rating >= 1 && rating < 2; + case "0": return rating < 1; + default: return true; + } + })(); + // Hide users with unknown roles if (roleName === "Desconhecido") return false; - return matchesSearch && matchesRole; + return matchesSearch && matchesRole && matchesRating; }); const stats = { @@ -474,25 +491,61 @@ export const TeamPage: React.FC = () => { {/* Filters and Search */}
-
-
- - setSearchTerm(e.target.value)} - className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold" - /> +
+ {/* Search and Add Button Row */} +
+
+ + setSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold" + /> +
+ +
- + {/* Filters Row */} +
+
+ + Filtros: +
+ + + + +
@@ -713,16 +766,16 @@ export const TeamPage: React.FC = () => {

Dados Bancários

- - setFormData({ ...formData, banco: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" /> + + setFormData({ ...formData, banco: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
- - setFormData({ ...formData, agencia: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" /> + + setFormData({ ...formData, agencia: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
- - setFormData({ ...formData, conta_pix: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" /> + + setFormData({ ...formData, conta_pix: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-brand-gold focus:ring focus:ring-brand-gold focus:ring-opacity-50 p-2 border" />
diff --git a/frontend/services/apiService.ts b/frontend/services/apiService.ts index aef7b3d..f5736bc 100644 --- a/frontend/services/apiService.ts +++ b/frontend/services/apiService.ts @@ -1,6 +1,9 @@ // Serviço para comunicação com o backend const API_BASE_URL = - import.meta.env.VITE_API_URL || "http://localhost:3000/api"; + import.meta.env.VITE_API_URL || "http://localhost:8080"; + +console.log('API_BASE_URL:', API_BASE_URL); +console.log('VITE_API_URL:', import.meta.env.VITE_API_URL); interface ApiResponse { data: T | null; @@ -420,6 +423,39 @@ export async function updateCadastroFot(id: string, data: any, token: string): P } } +/** + * Verifica se há eventos associados a um FOT + */ +export async function checkFotHasEvents(fotId: string, token: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/cadastro-fot/${fotId}/eventos`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + }); + + if (!response.ok) { + // Se o endpoint não existir, vamos usar a lista de eventos para verificar localmente + return { data: { hasEvents: false, eventCount: 0 }, error: null, isBackendDown: false }; + } + + const data = await response.json(); + return { + data: { hasEvents: data.count > 0, eventCount: data.count }, + error: null, + isBackendDown: false, + }; + } catch (error) { + return { + data: null, + error: error instanceof Error ? error.message : "Erro ao verificar eventos", + isBackendDown: true, + }; + } +} + /** * Remove um cadastro FOT */ diff --git a/frontend/types.ts b/frontend/types.ts index 08cbf3d..7bb1f36 100644 --- a/frontend/types.ts +++ b/frontend/types.ts @@ -132,6 +132,16 @@ export interface EventData { fotId?: string; // ID da Turma (FOT) typeId?: string; // ID do Tipo de Evento (UUID) + // Campos de gestão de equipe e recursos + qtdFormandos?: number; // Quantidade de formandos + qtdFotografos?: number; // Quantidade de fotógrafos necessários + qtdRecepcionistas?: number; // Quantidade de recepcionistas necessários + qtdCinegrafistas?: number; // Quantidade de cinegrafistas necessários + qtdEstudios?: number; // Quantidade de estúdios necessários + qtdPontosFoto?: number; // Quantidade de pontos de foto necessários + qtdPontosDecorados?: number; // Quantidade de pontos decorados necessários + qtdPontosLed?: number; // Quantidade de pontos LED necessários + // Fields populated from backend joins (ListAgendas) fot?: string; // Nome/Número da Turma (FOT) curso?: string; // Nome do Curso

- Nenhum profissional disponível para esta data + {professionals.length === 0 + ? "Nenhum profissional disponível para esta data" + : "Nenhum profissional encontrado com os filtros aplicados" + }

- Tente selecionar outra data ou entre em contato - com a equipe + {professionals.length === 0 + ? "Tente selecionar outra data ou entre em contato com a equipe" + : "Tente ajustar os filtros de busca" + }