Merge pull request #29 from rede5/Front-back-integracao-task9

Resolvido problemas críticos na experiência do usuário (Event Owner) ao criar eventos, garantindo consistência de dados, eliminando criações duplicadas e corrigindo o funcionamento dos filtros na dashboard.

Principais Alterações:

Correção de Duplicidade na Criação (Critical Bug):
Removida a chamada direta à API 
createAgenda
 dentro do componente 
EventForm
. A criação agora é centralizada via callback 
onSubmit
, evitando que o evento fosse submetido duas vezes (uma pelo formulário e outra pelo Dashboard/Context).
Consistência de Dados e Formatação:
Implementado reload automático (window.location.href = '/painel') após o sucesso da criação. Isso garante que a lista de eventos carregue os dados completos do backend (incluindo joins de Curso, Instituição e FOT), resolvendo o bug onde o evento aparecia incompleto na lista.
Corrigido o formato da data enviada ao backend (ISOString com sufixo Z), resolvendo o erro de parse RFC3339 no servidor Go.
Normalizada a chave de recuperação do token no localStorage (de @Photum:token para token).
Correção de Filtros (Dashboard):
Ajustado o filtro avançado de FOT para buscar pelo campo visual fot (Número do FOT) em vez do fotId (UUID interno).
Adicionado suporte a busca case-insensitive (ignora maiúsculas/minúsculas).
Arquivos Impactados:

frontend/components/EventForm.tsx
frontend/contexts/DataContext.tsx
frontend/pages/Dashboard.tsx
This commit is contained in:
Andre F. Rodrigues 2025-12-16 20:43:56 -03:00 committed by GitHub
commit ccb0871388
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 30 additions and 18 deletions

View file

@ -334,23 +334,13 @@ export const EventForm: React.FC<EventFormProps> = ({
pre_venda: true pre_venda: true
}; };
const authToken = userToken || localStorage.getItem("token") || ""; // Submit to parent handler
const response = await createAgenda(authToken, payload);
if (response.error) {
alert("Erro ao criar evento: " + response.error);
setShowToast(false);
return;
}
setTimeout(() => { setTimeout(() => {
if (onSubmit) { if (onSubmit) {
onSubmit(formData); onSubmit(formData);
} }
// Redirect or close is handled by parent, but we show success via toast usually
alert("Solicitação enviada com sucesso!"); alert("Solicitação enviada com sucesso!");
}, 1000); }, 1000);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
alert("Erro inesperado: " + e.message); alert("Erro inesperado: " + e.message);

View file

@ -107,7 +107,7 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
{/* Logo */} {/* Logo */}
<div <div
className="flex-shrink-0 flex items-center cursor-pointer" className="flex-shrink-0 flex items-center cursor-pointer"
onClick={() => onNavigate("home")} onClick={() => onNavigate("painel")}
> >
<img <img
src="/logo.png" src="/logo.png"

View file

@ -638,13 +638,35 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
const result = await getAgendas(visibleToken); const result = await getAgendas(visibleToken);
console.log("Raw Agenda Data:", result.data); // Debug logging console.log("Raw Agenda Data:", result.data); // Debug logging
if (result.data) { if (result.data) {
console.log("Sample event from backend:", result.data[0]); // DEBUG: Ver estrutura e status
// Map backend status to frontend EventStatus
const mapStatus = (backendStatus: string): EventStatus => {
const statusMap: Record<string, EventStatus> = {
"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) => ({ const mappedEvents: EventData[] = result.data.map((e: any) => ({
id: e.id, id: e.id,
name: e.observacoes_evento || e.tipo_evento_nome || "Evento sem nome", // Fallback mapping name: e.observacoes_evento || e.tipo_evento_nome || "Evento sem nome", // Fallback mapping
date: e.data_evento ? e.data_evento.split('T')[0] : "", date: e.data_evento ? e.data_evento.split('T')[0] : "",
time: e.horario || "00:00", time: e.horario || "00:00",
type: (e.tipo_evento_nome || "Outro") as EventType, // Map string to enum if possible, or keep string type: (e.tipo_evento_nome || "Outro") as EventType, // Map string to enum if possible, or keep string
status: EventStatus.PENDING_APPROVAL, status: mapStatus(e.status), // Map from backend status with fallback
address: { address: {
street: e.endereco ? e.endereco.split(',')[0] : "", street: e.endereco ? e.endereco.split(',')[0] : "",
number: e.endereco ? e.endereco.split(',')[1]?.split('-')[0]?.trim() || "" : "", number: e.endereco ? e.endereco.split(',')[1]?.split('-')[0]?.trim() || "" : "",
@ -764,7 +786,7 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
}, [token]); }, [token]);
const addEvent = async (event: EventData) => { const addEvent = async (event: EventData) => {
const token = localStorage.getItem("@Photum:token"); const token = localStorage.getItem("token");
if (!token) { if (!token) {
console.error("No token found"); console.error("No token found");
// Fallback for offline/mock // Fallback for offline/mock
@ -776,7 +798,7 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
// Map frontend fields (camelCase) to backend fields (snake_case) // Map frontend fields (camelCase) to backend fields (snake_case)
const payload = { const payload = {
fot_id: event.fotId, fot_id: event.fotId,
data_evento: event.date, // "YYYY-MM-DD" is acceptable data_evento: event.date + "T" + (event.time || "00:00") + ":00Z", // Backend expects full datetime with timezone
tipo_evento_id: event.typeId, tipo_evento_id: event.typeId,
observacoes_evento: event.name, // "Observações do Evento" maps to name in EventForm 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.street + ", " + event.address.number, // Or map separate fields if needed
@ -807,8 +829,8 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
if (result.data) { if (result.data) {
// Success // Success
console.log("Agenda criada:", result.data); console.log("Agenda criada:", result.data);
const newEvent = { ...event, id: result.data.id, status: EventStatus.PENDING_APPROVAL }; // Force reload to ensure complete data consistency (FOT, joins, etc.)
setEvents((prev) => [newEvent, ...prev]); window.location.href = '/painel';
} else { } else {
console.error("Erro ao criar agenda API:", result.error); console.error("Erro ao criar agenda API:", result.error);
// Fallback or Toast? // Fallback or Toast?

View file

@ -102,7 +102,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
!advancedFilters.date || e.date === advancedFilters.date; !advancedFilters.date || e.date === advancedFilters.date;
const matchesFot = const matchesFot =
!advancedFilters.fotId || !advancedFilters.fotId ||
String((e as any).fotId || "").includes(advancedFilters.fotId); String(e.fot || "").toLowerCase().includes(advancedFilters.fotId.toLowerCase());
const matchesType = const matchesType =
!advancedFilters.type || e.type === advancedFilters.type; !advancedFilters.type || e.type === advancedFilters.type;