From 9906db8bc62b7d87b84d97b3b0c205cb47cefbf1 Mon Sep 17 00:00:00 2001 From: NANDO9322 Date: Tue, 10 Feb 2026 16:36:57 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20destaca=20eventos=20de=20pr=C3=A9-venda?= =?UTF-8?q?=20e=20adiciona=20filtros=20de=20status=20da=20turma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Destaque em azul para pré-venda na grid. - Filtros por status (Pré-venda/Finalizada) no Dashboard. - Badges de status nos detalhes do evento. - Ajustes no backend para expor campo `pre_venda` da FOT. --- backend/internal/agenda/service.go | 5 ++- backend/internal/db/generated/agenda.sql.go | 9 ++++++ backend/internal/db/queries/agenda.sql | 3 ++ frontend/components/EventTable.tsx | 8 ++++- frontend/contexts/DataContext.tsx | 4 ++- frontend/pages/Dashboard.tsx | 34 +++++++++++++++++---- frontend/types.ts | 2 ++ 7 files changed, 56 insertions(+), 9 deletions(-) diff --git a/backend/internal/agenda/service.go b/backend/internal/agenda/service.go index 6985d06..089345c 100644 --- a/backend/internal/agenda/service.go +++ b/backend/internal/agenda/service.go @@ -78,6 +78,7 @@ type AgendaResponse struct { ParsedAssignments []Assignment `json:"assignments"` ParsedContacts []ContactInfo `json:"contacts"` ShadowFotFinalizada bool `json:"fot_finalizada"` + ShadowFotPreVenda bool `json:"fot_pre_venda"` } func (s *Service) CalculateStatus(fotoFaltante, recepFaltante, cineFaltante int32) string { @@ -205,6 +206,7 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string, regia EmpresaNome: r.EmpresaNome, EmpresaID: r.EmpresaID, FotFinalizada: r.FotFinalizada, + FotPreVenda: r.FotPreVenda, AnoSemestre: r.AnoSemestre, ObservacoesFot: r.ObservacoesFot, TipoEventoNome: r.TipoEventoNome, @@ -220,7 +222,7 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string, regia // DEBUG: Check first few rows for i, r := range rows { if i < 5 { - fmt.Printf("DEBUG SERVICE ADMIN PATH: Row %d ID=%s FotFinalizada=%v\n", i, r.ID.Bytes, r.FotFinalizada.Bool) + fmt.Printf("DEBUG SERVICE ADMIN PATH: Row %d ID=%s FotFinalizada=%v FotPreVenda=%v\n", i, r.ID.Bytes, r.FotFinalizada.Bool, r.FotPreVenda.Bool) } } } @@ -251,6 +253,7 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string, regia ParsedAssignments: assignments, ParsedContacts: contacts, ShadowFotFinalizada: row.FotFinalizada.Bool, + ShadowFotPreVenda: row.FotPreVenda.Bool, }) } diff --git a/backend/internal/db/generated/agenda.sql.go b/backend/internal/db/generated/agenda.sql.go index bd22680..0251d4a 100644 --- a/backend/internal/db/generated/agenda.sql.go +++ b/backend/internal/db/generated/agenda.sql.go @@ -439,6 +439,7 @@ SELECT te.nome as tipo_evento_nome, cf.empresa_id, cf.finalizada as fot_finalizada, + cf.pre_venda as fot_pre_venda, COALESCE( (SELECT json_agg(json_build_object( 'professional_id', ap.profissional_id, @@ -502,6 +503,7 @@ type ListAgendasRow struct { TipoEventoNome string `json:"tipo_evento_nome"` EmpresaID pgtype.UUID `json:"empresa_id"` FotFinalizada pgtype.Bool `json:"fot_finalizada"` + FotPreVenda pgtype.Bool `json:"fot_pre_venda"` AssignedProfessionals interface{} `json:"assigned_professionals"` } @@ -555,6 +557,7 @@ func (q *Queries) ListAgendas(ctx context.Context, regiao pgtype.Text) ([]ListAg &i.TipoEventoNome, &i.EmpresaID, &i.FotFinalizada, + &i.FotPreVenda, &i.AssignedProfessionals, ); err != nil { return nil, err @@ -579,6 +582,7 @@ SELECT te.nome as tipo_evento_nome, cf.empresa_id, cf.finalizada as fot_finalizada, + cf.pre_venda as fot_pre_venda, COALESCE( (SELECT json_agg(json_build_object( 'professional_id', ap.profissional_id, @@ -647,6 +651,7 @@ type ListAgendasByCompanyRow struct { TipoEventoNome string `json:"tipo_evento_nome"` EmpresaID pgtype.UUID `json:"empresa_id"` FotFinalizada pgtype.Bool `json:"fot_finalizada"` + FotPreVenda pgtype.Bool `json:"fot_pre_venda"` AssignedProfessionals interface{} `json:"assigned_professionals"` } @@ -700,6 +705,7 @@ func (q *Queries) ListAgendasByCompany(ctx context.Context, arg ListAgendasByCom &i.TipoEventoNome, &i.EmpresaID, &i.FotFinalizada, + &i.FotPreVenda, &i.AssignedProfessionals, ); err != nil { return nil, err @@ -827,6 +833,7 @@ SELECT te.nome as tipo_evento_nome, cf.empresa_id, cf.finalizada as fot_finalizada, + cf.pre_venda as fot_pre_venda, COALESCE( (SELECT json_agg(json_build_object( 'professional_id', ap.profissional_id, @@ -895,6 +902,7 @@ type ListAgendasByUserRow struct { TipoEventoNome string `json:"tipo_evento_nome"` EmpresaID pgtype.UUID `json:"empresa_id"` FotFinalizada pgtype.Bool `json:"fot_finalizada"` + FotPreVenda pgtype.Bool `json:"fot_pre_venda"` AssignedProfessionals interface{} `json:"assigned_professionals"` } @@ -948,6 +956,7 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, arg ListAgendasByUserPa &i.TipoEventoNome, &i.EmpresaID, &i.FotFinalizada, + &i.FotPreVenda, &i.AssignedProfessionals, ); err != nil { return nil, err diff --git a/backend/internal/db/queries/agenda.sql b/backend/internal/db/queries/agenda.sql index 551f0fc..e9a0834 100644 --- a/backend/internal/db/queries/agenda.sql +++ b/backend/internal/db/queries/agenda.sql @@ -46,6 +46,7 @@ SELECT te.nome as tipo_evento_nome, cf.empresa_id, cf.finalizada as fot_finalizada, + cf.pre_venda as fot_pre_venda, COALESCE( (SELECT json_agg(json_build_object( 'professional_id', ap.profissional_id, @@ -79,6 +80,7 @@ SELECT te.nome as tipo_evento_nome, cf.empresa_id, cf.finalizada as fot_finalizada, + cf.pre_venda as fot_pre_venda, COALESCE( (SELECT json_agg(json_build_object( 'professional_id', ap.profissional_id, @@ -228,6 +230,7 @@ SELECT te.nome as tipo_evento_nome, cf.empresa_id, cf.finalizada as fot_finalizada, + cf.pre_venda as fot_pre_venda, COALESCE( (SELECT json_agg(json_build_object( 'professional_id', ap.profissional_id, diff --git a/frontend/components/EventTable.tsx b/frontend/components/EventTable.tsx index 21ea37a..8b7d01c 100644 --- a/frontend/components/EventTable.tsx +++ b/frontend/components/EventTable.tsx @@ -541,7 +541,13 @@ export const EventTable: React.FC = ({ onEventClick(event)} - className={`cursor-pointer transition-colors ${event.fot_finalizada ? "bg-red-50 hover:bg-red-100 border-l-4 border-l-red-500" : "hover:bg-gray-50"}`} + className={`cursor-pointer transition-colors border-l-4 ${ + event.fot_finalizada + ? "bg-red-50 hover:bg-red-100 border-l-red-500" + : ((event.fot_pre_venda || event.pre_venda) && (userRole === UserRole.SUPERADMIN || userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.RESEARCHER)) + ? "bg-blue-50 hover:bg-blue-100 border-l-blue-500" + : "border-l-transparent hover:bg-gray-50" + }`} > diff --git a/frontend/contexts/DataContext.tsx b/frontend/contexts/DataContext.tsx index 5fa64ea..4a455d2 100644 --- a/frontend/contexts/DataContext.tsx +++ b/frontend/contexts/DataContext.tsx @@ -724,7 +724,9 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ typeId: e.tipo_evento_id, local_evento: e.local_evento, // Added local_evento mapping fot_finalizada: e.fot_finalizada, // Mapped from backend - assignments: Array.isArray(e.assigned_professionals) + 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, diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 38712d6..2123952 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -438,9 +438,16 @@ export const Dashboard: React.FC = ({ !advancedFilters.company || e.empresa === advancedFilters.company; const matchesInstitution = !advancedFilters.institution || e.instituicao === advancedFilters.institution; + + // New FOT Status Filter + const matchesFotStatus = + !advancedFilters.fotStatus || + (advancedFilters.fotStatus === 'finalizada' && e.fot_finalizada) || + (advancedFilters.fotStatus === 'pre_venda' && (e.fot_pre_venda || e.pre_venda)) || + (advancedFilters.fotStatus === 'normal' && !e.fot_finalizada && !e.fot_pre_venda && !e.pre_venda); return ( - matchesSearch && matchesStatus && matchesDate && matchesFot && matchesType && matchesCompany && matchesInstitution + matchesSearch && matchesStatus && matchesDate && matchesFot && matchesType && matchesCompany && matchesInstitution && matchesFotStatus ); }); @@ -883,11 +890,26 @@ export const Dashboard: React.FC = ({ -
- {selectedEvent.status} +
+ {/* Badge de Finalizada */} + {(selectedEvent.fot_finalizada) && ( +
+ Turma Finalizada +
+ )} + {/* Badge de Pré-Venda */} + {((selectedEvent.fot_pre_venda || selectedEvent.pre_venda) && !selectedEvent.fot_finalizada) && ( +
+ Pré-Venda +
+ )} + +
+ {selectedEvent.status} +
diff --git a/frontend/types.ts b/frontend/types.ts index 7521155..c8bc165 100644 --- a/frontend/types.ts +++ b/frontend/types.ts @@ -151,6 +151,8 @@ export interface EventData { courseId?: string; // ID do curso/turma relacionado ao evento fotId?: string; // ID da Turma (FOT) fot_finalizada?: boolean; // Status da Turma (Visualizacao no Dashboard) + fot_pre_venda?: boolean; // Status de Pré-Venda (vindo do FOT) + pre_venda?: boolean; // Status de Pré-Venda typeId?: string; // ID do Tipo de Evento (UUID) // Campos de gestão de equipe e recursos