feat(ux): melhorias de navegação, formulário e fluxo de eventos

Backend:
- Adiciona campo `contacts` (JSONB) na tabela `agendas` e atualiza lógica de criação.

Frontend:
- Adiciona campos dinâmicos de contato no formulário de Novo Evento.
- Otimiza busca do Mapbox priorizando a região selecionada (SP/MG).
- Implementa "Deep Linking" no Dashboard (abrir detalhes do evento direto via URL).
- Corrige "flicker" (piscada da lista) ao carregar detalhes via link permitindo carregamento suave.
- Adiciona botão "Aprovar" e fluxo de aprovação na visualização de detalhes.
- Corrige fluxo de edição (salvar retorna para detalhes sem recarregar a página).
- Corrige navegação dos botões "Voltar" em Detalhes e Logística para retornarem corretamente à lista/painel.
- Melhora layout do cabeçalho de detalhes (remove ID vazio e unifica títulos duplicados).
- Ajusta clique no Logo para forçar reset da navegação para o Painel.
This commit is contained in:
NANDO9322 2026-02-06 13:32:11 -03:00
parent e86bd0a570
commit 21987d221e
13 changed files with 208 additions and 61 deletions

View file

@ -32,30 +32,37 @@ func NewService(db *generated.Queries, notif *notification.Service, cfg *config.
} }
} }
type ContactInfo struct {
Name string `json:"name"`
Role string `json:"role"`
Phone string `json:"phone"`
}
type CreateAgendaRequest struct { type CreateAgendaRequest struct {
FotID uuid.UUID `json:"fot_id"` FotID uuid.UUID `json:"fot_id"`
DataEvento time.Time `json:"data_evento"` DataEvento time.Time `json:"data_evento"`
TipoEventoID uuid.UUID `json:"tipo_evento_id"` TipoEventoID uuid.UUID `json:"tipo_evento_id"`
ObservacoesEvento string `json:"observacoes_evento"` ObservacoesEvento string `json:"observacoes_evento"`
LocalEvento string `json:"local_evento"` LocalEvento string `json:"local_evento"`
Endereco string `json:"endereco"` Endereco string `json:"endereco"`
Horario string `json:"horario"` Horario string `json:"horario"`
QtdFormandos int32 `json:"qtd_formandos"` QtdFormandos int32 `json:"qtd_formandos"`
QtdFotografos int32 `json:"qtd_fotografos"` QtdFotografos int32 `json:"qtd_fotografos"`
QtdRecepcionistas int32 `json:"qtd_recepcionistas"` QtdRecepcionistas int32 `json:"qtd_recepcionistas"`
QtdCinegrafistas int32 `json:"qtd_cinegrafistas"` QtdCinegrafistas int32 `json:"qtd_cinegrafistas"`
QtdEstudios int32 `json:"qtd_estudios"` QtdEstudios int32 `json:"qtd_estudios"`
QtdPontoFoto int32 `json:"qtd_ponto_foto"` QtdPontoFoto int32 `json:"qtd_ponto_foto"`
QtdPontoID int32 `json:"qtd_ponto_id"` QtdPontoID int32 `json:"qtd_ponto_id"`
QtdPontoDecorado int32 `json:"qtd_ponto_decorado"` QtdPontoDecorado int32 `json:"qtd_ponto_decorado"`
QtdPontosLed int32 `json:"qtd_pontos_led"` QtdPontosLed int32 `json:"qtd_pontos_led"`
QtdPlataforma360 int32 `json:"qtd_plataforma_360"` QtdPlataforma360 int32 `json:"qtd_plataforma_360"`
StatusProfissionais string `json:"status_profissionais"` StatusProfissionais string `json:"status_profissionais"`
FotoFaltante int32 `json:"foto_faltante"` FotoFaltante int32 `json:"foto_faltante"`
RecepFaltante int32 `json:"recep_faltante"` RecepFaltante int32 `json:"recep_faltante"`
CineFaltante int32 `json:"cine_faltante"` CineFaltante int32 `json:"cine_faltante"`
LogisticaObservacoes string `json:"logistica_observacoes"` LogisticaObservacoes string `json:"logistica_observacoes"`
PreVenda bool `json:"pre_venda"` PreVenda bool `json:"pre_venda"`
Contatos []ContactInfo `json:"contatos"`
} }
type Assignment struct { type Assignment struct {
@ -67,7 +74,8 @@ type Assignment struct {
type AgendaResponse struct { type AgendaResponse struct {
generated.ListAgendasRow generated.ListAgendasRow
ParsedAssignments []Assignment `json:"assignments"` ParsedAssignments []Assignment `json:"assignments"`
ParsedContacts []ContactInfo `json:"contacts"`
} }
func (s *Service) CalculateStatus(fotoFaltante, recepFaltante, cineFaltante int32) string { func (s *Service) CalculateStatus(fotoFaltante, recepFaltante, cineFaltante int32) string {
@ -88,6 +96,8 @@ func (s *Service) CalculateStatus(fotoFaltante, recepFaltante, cineFaltante int3
func (s *Service) Create(ctx context.Context, userID uuid.UUID, req CreateAgendaRequest, regiao string) (generated.Agenda, error) { func (s *Service) Create(ctx context.Context, userID uuid.UUID, req CreateAgendaRequest, regiao string) (generated.Agenda, error) {
status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante) status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante)
contatosBytes, _ := json.Marshal(req.Contatos)
params := generated.CreateAgendaParams{ params := generated.CreateAgendaParams{
FotID: pgtype.UUID{Bytes: req.FotID, Valid: true}, FotID: pgtype.UUID{Bytes: req.FotID, Valid: true},
DataEvento: pgtype.Date{Time: req.DataEvento, Valid: true}, DataEvento: pgtype.Date{Time: req.DataEvento, Valid: true},
@ -114,6 +124,7 @@ func (s *Service) Create(ctx context.Context, userID uuid.UUID, req CreateAgenda
PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true}, PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true},
UserID: pgtype.UUID{Bytes: userID, Valid: true}, UserID: pgtype.UUID{Bytes: userID, Valid: true},
Regiao: pgtype.Text{String: regiao, Valid: true}, Regiao: pgtype.Text{String: regiao, Valid: true},
Contatos: contatosBytes,
} }
return s.queries.CreateAgenda(ctx, params) return s.queries.CreateAgenda(ctx, params)
@ -183,6 +194,7 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string, regia
ObservacoesFot: r.ObservacoesFot, ObservacoesFot: r.ObservacoesFot,
TipoEventoNome: r.TipoEventoNome, TipoEventoNome: r.TipoEventoNome,
AssignedProfessionals: r.AssignedProfessionals, AssignedProfessionals: r.AssignedProfessionals,
Contatos: r.Contatos,
}) })
} }
} else { } else {
@ -207,9 +219,16 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string, regia
json.Unmarshal(bytes, &assignments) json.Unmarshal(bytes, &assignments)
} }
} }
var contacts []ContactInfo
if len(row.Contatos) > 0 {
json.Unmarshal(row.Contatos, &contacts)
}
response = append(response, AgendaResponse{ response = append(response, AgendaResponse{
ListAgendasRow: row, ListAgendasRow: row,
ParsedAssignments: assignments, ParsedAssignments: assignments,
ParsedContacts: contacts,
}) })
} }
@ -233,6 +252,8 @@ func (s *Service) Update(ctx context.Context, id uuid.UUID, req CreateAgendaRequ
status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante) status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante)
contatosBytes, _ := json.Marshal(req.Contatos)
params := generated.UpdateAgendaParams{ params := generated.UpdateAgendaParams{
ID: pgtype.UUID{Bytes: id, Valid: true}, ID: pgtype.UUID{Bytes: id, Valid: true},
FotID: pgtype.UUID{Bytes: req.FotID, Valid: true}, FotID: pgtype.UUID{Bytes: req.FotID, Valid: true},
@ -259,6 +280,7 @@ func (s *Service) Update(ctx context.Context, id uuid.UUID, req CreateAgendaRequ
LogisticaObservacoes: pgtype.Text{String: req.LogisticaObservacoes, Valid: req.LogisticaObservacoes != ""}, LogisticaObservacoes: pgtype.Text{String: req.LogisticaObservacoes, Valid: req.LogisticaObservacoes != ""},
PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true}, PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true},
Regiao: pgtype.Text{String: regiao, Valid: true}, Regiao: pgtype.Text{String: regiao, Valid: true},
Contatos: contatosBytes,
} }
return s.queries.UpdateAgenda(ctx, params) return s.queries.UpdateAgenda(ctx, params)
} }

View file

@ -97,10 +97,11 @@ INSERT INTO agenda (
logistica_observacoes, logistica_observacoes,
pre_venda, pre_venda,
user_id, user_id,
regiao regiao,
contatos
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25 $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $26, $25
) RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao ) RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao, contatos
` `
type CreateAgendaParams struct { type CreateAgendaParams struct {
@ -128,6 +129,7 @@ type CreateAgendaParams struct {
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
PreVenda pgtype.Bool `json:"pre_venda"` PreVenda pgtype.Bool `json:"pre_venda"`
UserID pgtype.UUID `json:"user_id"` UserID pgtype.UUID `json:"user_id"`
Contatos []byte `json:"contatos"`
Regiao pgtype.Text `json:"regiao"` Regiao pgtype.Text `json:"regiao"`
} }
@ -157,6 +159,7 @@ func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Age
arg.LogisticaObservacoes, arg.LogisticaObservacoes,
arg.PreVenda, arg.PreVenda,
arg.UserID, arg.UserID,
arg.Contatos,
arg.Regiao, arg.Regiao,
) )
var i Agenda var i Agenda
@ -191,6 +194,7 @@ func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Age
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
) )
return i, err return i, err
} }
@ -211,7 +215,7 @@ func (q *Queries) DeleteAgenda(ctx context.Context, arg DeleteAgendaParams) erro
} }
const getAgenda = `-- name: GetAgenda :one const getAgenda = `-- name: GetAgenda :one
SELECT id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao FROM agenda SELECT id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao, contatos FROM agenda
WHERE id = $1 AND regiao = $2 LIMIT 1 WHERE id = $1 AND regiao = $2 LIMIT 1
` `
@ -254,12 +258,13 @@ func (q *Queries) GetAgenda(ctx context.Context, arg GetAgendaParams) (Agenda, e
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
) )
return i, err return i, err
} }
const getAgendaByFotDataTipo = `-- name: GetAgendaByFotDataTipo :one const getAgendaByFotDataTipo = `-- name: GetAgendaByFotDataTipo :one
SELECT id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao FROM agenda SELECT id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao, contatos FROM agenda
WHERE fot_id = $1 AND data_evento = $2 AND tipo_evento_id = $3 AND regiao = $4 WHERE fot_id = $1 AND data_evento = $2 AND tipo_evento_id = $3 AND regiao = $4
LIMIT 1 LIMIT 1
` `
@ -310,6 +315,7 @@ func (q *Queries) GetAgendaByFotDataTipo(ctx context.Context, arg GetAgendaByFot
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
) )
return i, err return i, err
} }
@ -413,7 +419,7 @@ func (q *Queries) GetAgendaProfessionals(ctx context.Context, agendaID pgtype.UU
const listAgendas = `-- name: ListAgendas :many const listAgendas = `-- name: ListAgendas :many
SELECT SELECT
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.contatos,
cf.fot as fot_numero, cf.fot as fot_numero,
cf.instituicao, cf.instituicao,
c.nome as curso_nome, c.nome as curso_nome,
@ -474,6 +480,7 @@ type ListAgendasRow struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
Regiao pgtype.Text `json:"regiao"` Regiao pgtype.Text `json:"regiao"`
Contatos []byte `json:"contatos"`
FotNumero string `json:"fot_numero"` FotNumero string `json:"fot_numero"`
Instituicao pgtype.Text `json:"instituicao"` Instituicao pgtype.Text `json:"instituicao"`
CursoNome string `json:"curso_nome"` CursoNome string `json:"curso_nome"`
@ -525,6 +532,7 @@ func (q *Queries) ListAgendas(ctx context.Context, regiao pgtype.Text) ([]ListAg
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
&i.FotNumero, &i.FotNumero,
&i.Instituicao, &i.Instituicao,
&i.CursoNome, &i.CursoNome,
@ -547,7 +555,7 @@ func (q *Queries) ListAgendas(ctx context.Context, regiao pgtype.Text) ([]ListAg
const listAgendasByCompany = `-- name: ListAgendasByCompany :many const listAgendasByCompany = `-- name: ListAgendasByCompany :many
SELECT SELECT
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.contatos,
cf.fot as fot_numero, cf.fot as fot_numero,
cf.instituicao, cf.instituicao,
c.nome as curso_nome, c.nome as curso_nome,
@ -613,6 +621,7 @@ type ListAgendasByCompanyRow struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
Regiao pgtype.Text `json:"regiao"` Regiao pgtype.Text `json:"regiao"`
Contatos []byte `json:"contatos"`
FotNumero string `json:"fot_numero"` FotNumero string `json:"fot_numero"`
Instituicao pgtype.Text `json:"instituicao"` Instituicao pgtype.Text `json:"instituicao"`
CursoNome string `json:"curso_nome"` CursoNome string `json:"curso_nome"`
@ -664,6 +673,7 @@ func (q *Queries) ListAgendasByCompany(ctx context.Context, arg ListAgendasByCom
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
&i.FotNumero, &i.FotNumero,
&i.Instituicao, &i.Instituicao,
&i.CursoNome, &i.CursoNome,
@ -686,7 +696,7 @@ func (q *Queries) ListAgendasByCompany(ctx context.Context, arg ListAgendasByCom
const listAgendasByFot = `-- name: ListAgendasByFot :many const listAgendasByFot = `-- name: ListAgendasByFot :many
SELECT SELECT
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.contatos,
te.nome as tipo_evento_nome te.nome as tipo_evento_nome
FROM agenda a FROM agenda a
JOIN tipos_eventos te ON a.tipo_evento_id = te.id JOIN tipos_eventos te ON a.tipo_evento_id = te.id
@ -730,6 +740,7 @@ type ListAgendasByFotRow struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
Regiao pgtype.Text `json:"regiao"` Regiao pgtype.Text `json:"regiao"`
Contatos []byte `json:"contatos"`
TipoEventoNome string `json:"tipo_evento_nome"` TipoEventoNome string `json:"tipo_evento_nome"`
} }
@ -773,6 +784,7 @@ func (q *Queries) ListAgendasByFot(ctx context.Context, arg ListAgendasByFotPara
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
&i.TipoEventoNome, &i.TipoEventoNome,
); err != nil { ); err != nil {
return nil, err return nil, err
@ -787,7 +799,7 @@ func (q *Queries) ListAgendasByFot(ctx context.Context, arg ListAgendasByFotPara
const listAgendasByUser = `-- name: ListAgendasByUser :many const listAgendasByUser = `-- name: ListAgendasByUser :many
SELECT SELECT
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em, a.status, a.logistica_notificacao_enviada_em, a.regiao, a.contatos,
cf.fot as fot_numero, cf.fot as fot_numero,
cf.instituicao, cf.instituicao,
c.nome as curso_nome, c.nome as curso_nome,
@ -853,6 +865,7 @@ type ListAgendasByUserRow struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
Regiao pgtype.Text `json:"regiao"` Regiao pgtype.Text `json:"regiao"`
Contatos []byte `json:"contatos"`
FotNumero string `json:"fot_numero"` FotNumero string `json:"fot_numero"`
Instituicao pgtype.Text `json:"instituicao"` Instituicao pgtype.Text `json:"instituicao"`
CursoNome string `json:"curso_nome"` CursoNome string `json:"curso_nome"`
@ -904,6 +917,7 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, arg ListAgendasByUserPa
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
&i.FotNumero, &i.FotNumero,
&i.Instituicao, &i.Instituicao,
&i.CursoNome, &i.CursoNome,
@ -1083,9 +1097,10 @@ SET
cine_faltante = $22, cine_faltante = $22,
logistica_observacoes = $23, logistica_observacoes = $23,
pre_venda = $24, pre_venda = $24,
contatos = $25,
atualizado_em = NOW() atualizado_em = NOW()
WHERE id = $1 AND regiao = $25 WHERE id = $1 AND regiao = $26
RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao, contatos
` `
type UpdateAgendaParams struct { type UpdateAgendaParams struct {
@ -1113,6 +1128,7 @@ type UpdateAgendaParams struct {
CineFaltante pgtype.Int4 `json:"cine_faltante"` CineFaltante pgtype.Int4 `json:"cine_faltante"`
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
PreVenda pgtype.Bool `json:"pre_venda"` PreVenda pgtype.Bool `json:"pre_venda"`
Contatos []byte `json:"contatos"`
Regiao pgtype.Text `json:"regiao"` Regiao pgtype.Text `json:"regiao"`
} }
@ -1142,6 +1158,7 @@ func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Age
arg.CineFaltante, arg.CineFaltante,
arg.LogisticaObservacoes, arg.LogisticaObservacoes,
arg.PreVenda, arg.PreVenda,
arg.Contatos,
arg.Regiao, arg.Regiao,
) )
var i Agenda var i Agenda
@ -1176,6 +1193,7 @@ func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Age
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
) )
return i, err return i, err
} }
@ -1184,7 +1202,7 @@ const updateAgendaStatus = `-- name: UpdateAgendaStatus :one
UPDATE agenda UPDATE agenda
SET status = $2, atualizado_em = NOW() SET status = $2, atualizado_em = NOW()
WHERE id = $1 AND regiao = $3 WHERE id = $1 AND regiao = $3
RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em, status, logistica_notificacao_enviada_em, regiao, contatos
` `
type UpdateAgendaStatusParams struct { type UpdateAgendaStatusParams struct {
@ -1227,6 +1245,7 @@ func (q *Queries) UpdateAgendaStatus(ctx context.Context, arg UpdateAgendaStatus
&i.Status, &i.Status,
&i.LogisticaNotificacaoEnviadaEm, &i.LogisticaNotificacaoEnviadaEm,
&i.Regiao, &i.Regiao,
&i.Contatos,
) )
return i, err return i, err
} }

View file

@ -39,6 +39,7 @@ type Agenda struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
Regiao pgtype.Text `json:"regiao"` Regiao pgtype.Text `json:"regiao"`
Contatos []byte `json:"contatos"`
} }
type AgendaEscala struct { type AgendaEscala struct {

View file

@ -0,0 +1 @@
ALTER TABLE agenda ADD COLUMN IF NOT EXISTS contatos JSONB DEFAULT '[]'::jsonb;

View file

@ -24,9 +24,10 @@ INSERT INTO agenda (
logistica_observacoes, logistica_observacoes,
pre_venda, pre_venda,
user_id, user_id,
regiao regiao,
contatos
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, @regiao $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, @regiao, $25
) RETURNING *; ) RETURNING *;
-- name: GetAgenda :one -- name: GetAgenda :one
@ -121,6 +122,7 @@ SET
cine_faltante = $22, cine_faltante = $22,
logistica_observacoes = $23, logistica_observacoes = $23,
pre_venda = $24, pre_venda = $24,
contatos = $25,
atualizado_em = NOW() atualizado_em = NOW()
WHERE id = $1 AND regiao = @regiao WHERE id = $1 AND regiao = @regiao
RETURNING *; RETURNING *;

View file

@ -231,7 +231,8 @@ CREATE TABLE IF NOT EXISTS agenda (
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status VARCHAR(50) DEFAULT 'Pendente', -- Pendente, Aprovado, Arquivado status VARCHAR(50) DEFAULT 'Pendente', -- Pendente, Aprovado, Arquivado
logistica_notificacao_enviada_em TIMESTAMP, logistica_notificacao_enviada_em TIMESTAMP,
regiao CHAR(2) DEFAULT 'SP' regiao CHAR(2) DEFAULT 'SP',
contatos JSONB DEFAULT '[]'::jsonb
); );
CREATE TABLE IF NOT EXISTS agenda_profissionais ( CREATE TABLE IF NOT EXISTS agenda_profissionais (

View file

@ -398,8 +398,8 @@ export const EventForm: React.FC<EventFormProps> = ({
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
// Validation // Validation (Observations are optional)
if (!formData.name) return alert("Preencha o tipo de evento"); // if (!formData.name) return alert("Preencha o tipo de evento");
if (!formData.date) return alert("Preencha a data"); if (!formData.date) return alert("Preencha a data");
if (!formData.attendees || parseInt(formData.attendees) <= 0) return alert("Preencha o número de formandos"); if (!formData.attendees || parseInt(formData.attendees) <= 0) return alert("Preencha o número de formandos");
@ -455,7 +455,8 @@ export const EventForm: React.FC<EventFormProps> = ({
status_profissionais: "PENDING", status_profissionais: "PENDING",
cine_faltante: 0, cine_faltante: 0,
logistica_observacoes: "", logistica_observacoes: "",
pre_venda: true pre_venda: true,
contatos: formData.contacts
}; };
// Submit to parent handler // Submit to parent handler
@ -1124,7 +1125,7 @@ export const EventForm: React.FC<EventFormProps> = ({
className="w-full border border-gray-300 rounded-sm p-3 focus:outline-none focus:border-brand-gold h-32 text-sm" className="w-full border border-gray-300 rounded-sm p-3 focus:outline-none focus:border-brand-gold h-32 text-sm"
placeholder={ placeholder={
isClientRequest isClientRequest
? "Qual o estilo do casamento? Quais fotos são indispensáveis? Fale um pouco sobre vocês..." ? "Fale sobre..."
: "Instruções técnicas..." : "Instruções técnicas..."
} }
value={formData.briefing} value={formData.briefing}
@ -1169,6 +1170,16 @@ export const EventForm: React.FC<EventFormProps> = ({
setFormData({ ...formData, contacts: newContacts }); setFormData({ ...formData, contacts: newContacts });
}} }}
/> />
<Input
label={idx === 0 ? "Telefone" : ""}
placeholder="(11) 9..."
value={contact.phone}
onChange={(e) => {
const newContacts = [...formData.contacts];
newContacts[idx].phone = e.target.value;
setFormData({ ...formData, contacts: newContacts });
}}
/>
<button <button
onClick={() => removeContact(idx)} onClick={() => removeContact(idx)}
className={`mt-1 p-2 text-gray-400 hover:text-red-500 ${idx === 0 ? "mt-7" : "" className={`mt-1 p-2 text-gray-400 hover:text-red-500 ${idx === 0 ? "mt-7" : ""

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Plus, Trash, User, Truck, Car, Send } from "lucide-react"; import { Plus, Trash, User, Truck, Car, Send, ArrowLeft } from "lucide-react";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
import { listCarros, createCarro, deleteCarro, addPassenger, removePassenger, listPassengers, listCarros as fetchCarrosApi, notifyLogistics } from "../services/apiService"; import { listCarros, createCarro, deleteCarro, addPassenger, removePassenger, listPassengers, listCarros as fetchCarrosApi, notifyLogistics } from "../services/apiService";
import { useData } from "../contexts/DataContext"; import { useData } from "../contexts/DataContext";
@ -202,6 +202,13 @@ const EventLogistics: React.FC<EventLogisticsProps> = ({ agendaId, isEditable: p
Notificação enviada em: {new Date(eventData.logisticaNotificacaoEnviadaEm).toLocaleString()} Notificação enviada em: {new Date(eventData.logisticaNotificacaoEnviadaEm).toLocaleString()}
</div> </div>
)} )}
<button
onClick={() => window.location.href = `/painel?eventId=${agendaId}`}
className="ml-auto flex items-center gap-2 px-3 py-1.5 bg-white border border-gray-300 rounded text-sm text-gray-700 hover:bg-gray-50 transition-colors shadow-sm"
>
<ArrowLeft size={16} />
Voltar
</button>
</div> </div>
{/* Add Car Form - Only for Admins */} {/* Add Car Form - Only for Admins */}

View file

@ -151,8 +151,11 @@ export const EventTable: React.FC<EventTableProps> = ({
bValue = (b as any).fotId || ""; bValue = (b as any).fotId || "";
break; break;
case "date": case "date":
aValue = new Date(a.date + "T00:00:00").getTime(); // Parse date robustly
bValue = new Date(b.date + "T00:00:00").getTime(); const dateA = a.date ? new Date(a.date.includes('T') ? a.date : a.date + "T00:00:00") : new Date(0);
const dateB = b.date ? new Date(b.date.includes('T') ? b.date : b.date + "T00:00:00") : new Date(0);
aValue = !isNaN(dateA.getTime()) ? dateA.getTime() : 0;
bValue = !isNaN(dateB.getTime()) ? dateB.getTime() : 0;
break; break;
case "curso": case "curso":
aValue = ((a as any).curso || "").toLowerCase(); aValue = ((a as any).curso || "").toLowerCase();

View file

@ -157,7 +157,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={() => navigate("/painel")} onClick={() => window.location.href = "/painel"}
> >
<img <img
src="/logo.png" src="/logo.png"

View file

@ -69,7 +69,8 @@ export const Dashboard: React.FC<DashboardProps> = ({
// For now, trust DataContext + local state update. // For now, trust DataContext + local state update.
// Actually, DataContext refetch logic was "try import...", so it might be async. // Actually, DataContext refetch logic was "try import...", so it might be async.
// Let's reload window to be 100% sure for the user as requested "mudei a data e não mudou". // Let's reload window to be 100% sure for the user as requested "mudei a data e não mudou".
window.location.reload(); // setView("details"); // Already called above
// removing window.location.reload() to maintain SPA feel
} else { } else {
console.error("Update function not available"); console.error("Update function not available");
} }
@ -89,11 +90,33 @@ export const Dashboard: React.FC<DashboardProps> = ({
setView("list"); setView("list");
} }
}; };
const [view, setView] = useState<"list" | "create" | "edit" | "details">( const [view, setView] = useState<"list" | "create" | "edit" | "details">(() => {
initialView const params = new URLSearchParams(window.location.search);
); return params.get("eventId") ? "details" : initialView;
});
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [selectedEvent, setSelectedEvent] = useState<EventData | null>(null); const [selectedEvent, setSelectedEvent] = useState<EventData | null>(() => {
const params = new URLSearchParams(window.location.search);
const eventId = params.get("eventId");
if (eventId && events.length > 0) {
return events.find(e => e.id === eventId) || null;
}
return null;
});
// Effect to sync selectedEvent if events load LATER than initial render
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const eventId = params.get("eventId");
if (eventId && events.length > 0 && !selectedEvent) {
const found = events.find(e => e.id === eventId);
if (found) {
setSelectedEvent(found);
// Ensure view is details if we just found it
setView("details");
}
}
}, [events, window.location.search]);
const [activeFilter, setActiveFilter] = useState<string>("all"); const [activeFilter, setActiveFilter] = useState<string>("all");
const [advancedFilters, setAdvancedFilters] = useState<EventFilters>({ const [advancedFilters, setAdvancedFilters] = useState<EventFilters>({
date: "", date: "",
@ -331,7 +354,16 @@ export const Dashboard: React.FC<DashboardProps> = ({
}; };
}, [myEvents]); }, [myEvents]);
// Filter Logic // Removed the previous useEffect for eventId to avoid conflicts with the new initialization logic
// and separate sync effect above.
/*
// Check for eventId in URL on mount
useEffect(() => {
...
}, [events, window.location.search]);
*/
// Combined filtering logic
const filteredEvents = myEvents.filter((e) => { const filteredEvents = myEvents.filter((e) => {
const matchesSearch = e.name const matchesSearch = e.name
.toLowerCase() .toLowerCase()
@ -490,6 +522,17 @@ export const Dashboard: React.FC<DashboardProps> = ({
); );
}; };
const handleApproveEvent = async (e: React.MouseEvent) => {
e.stopPropagation();
if (selectedEvent) {
if(window.confirm("Tem certeza que deseja aprovar este evento?")) {
await updateEventStatus(selectedEvent.id, EventStatus.CONFIRMED);
// Optimistically update
setSelectedEvent({ ...selectedEvent, status: EventStatus.CONFIRMED });
}
}
};
const handleManageTeam = () => { const handleManageTeam = () => {
setIsTeamModalOpen(true); setIsTeamModalOpen(true);
}; };
@ -594,7 +637,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
<div className="min-h-screen bg-gray-50 pt-20 sm:pt-24 md:pt-28 lg:pt-32 pb-8 sm:pb-12 px-3 sm:px-4 lg:px-6"> <div className="min-h-screen bg-gray-50 pt-20 sm:pt-24 md:pt-28 lg:pt-32 pb-8 sm:pb-12 px-3 sm:px-4 lg:px-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Header */}
{view === "list" && ( {view === "list" && !new URLSearchParams(window.location.search).get("eventId") && (
<div className="flex flex-col md:flex-row md:items-center justify-between mb-6 sm:mb-8 gap-3 sm:gap-4 fade-in"> <div className="flex flex-col md:flex-row md:items-center justify-between mb-6 sm:mb-8 gap-3 sm:gap-4 fade-in">
{renderRoleSpecificHeader()} {renderRoleSpecificHeader()}
{renderRoleSpecificActions()} {renderRoleSpecificActions()}
@ -602,7 +645,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
)} )}
{/* Content Switcher */} {/* Content Switcher */}
{view === "list" && ( {view === "list" && !new URLSearchParams(window.location.search).get("eventId") && (
<div className="space-y-6 fade-in"> <div className="space-y-6 fade-in">
{/* Search Bar */} {/* Search Bar */}
<div className="flex flex-col sm:flex-row gap-4 items-center justify-between bg-gray-50 p-3 rounded-lg border border-gray-100"> <div className="flex flex-col sm:flex-row gap-4 items-center justify-between bg-gray-50 p-3 rounded-lg border border-gray-100">
@ -688,6 +731,15 @@ export const Dashboard: React.FC<DashboardProps> = ({
/> />
)} )}
{/* Loading State for Deep Link */ }
{(!!new URLSearchParams(window.location.search).get("eventId") && (!selectedEvent || view !== "details")) && (
<div className="flex flex-col items-center justify-center py-20 fade-in">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-brand-purple mb-4"></div>
<p className="text-gray-500">Carregando detalhes do evento...</p>
</div>
)}
{view === "details" && selectedEvent && ( {view === "details" && selectedEvent && (
<div className="fade-in"> <div className="fade-in">
<Button <Button
@ -792,6 +844,18 @@ export const Dashboard: React.FC<DashboardProps> = ({
<Map size={16} className="mr-2" /> Abrir no Maps <Map size={16} className="mr-2" /> Abrir no Maps
</Button> </Button>
{/* Botão de Aprovação (Apenas Admin/Owner se pendente) */}
{(user.role === UserRole.BUSINESS_OWNER || user.role === UserRole.SUPERADMIN) &&
selectedEvent.status === EventStatus.PENDING_APPROVAL && (
<Button
variant="primary"
onClick={handleApproveEvent}
className="text-sm bg-green-600 text-white hover:bg-green-700 border-green-600"
>
<CheckCircle size={16} className="mr-2" /> Aprovar
</Button>
)}
<Button <Button
variant="default" variant="default"
onClick={() => { onClick={() => {

View file

@ -29,23 +29,29 @@ const EventDetails: React.FC = () => {
const formattedDate = new Date(event.date + "T00:00:00").toLocaleDateString(); const formattedDate = new Date(event.date + "T00:00:00").toLocaleDateString();
return ( return (
<div className="min-h-screen bg-gray-50 p-6"> <div className="min-h-screen bg-gray-50 p-6 pt-24">
<div className="max-w-7xl mx-auto space-y-6"> <div className="max-w-7xl mx-auto space-y-6">
{/* Header */} {/* Header */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<button onClick={() => navigate('/eventos')} className="flex items-center gap-2 px-3 py-2 hover:bg-gray-200 rounded-lg transition-colors text-gray-600"> <button onClick={() => window.location.href = `/painel?eventId=${id}`} className="flex items-center gap-2 px-3 py-2 hover:bg-gray-200 rounded-lg transition-colors text-gray-600">
<ArrowLeft className="w-5 h-5" /> <ArrowLeft className="w-5 h-5" />
<span className="font-medium">Voltar</span> <span className="font-medium">Voltar</span>
</button> </button>
<div className="w-px h-8 bg-gray-300 mx-2 hidden sm:block"></div> <div className="w-px h-8 bg-gray-300 mx-2 hidden sm:block"></div>
<div> <div>
<h1 className="text-2xl font-bold text-gray-800 flex items-center gap-2"> <h1 className="text-2xl font-bold text-gray-800 flex items-center gap-2">
{event.empresa_nome} - {event.tipo_evento_nome} {(() => {
const name = event.empresa_nome || event.name;
const type = event.tipo_evento_nome || event.type;
return name === type ? name : `${name} - ${type}`;
})()}
<span className={`text-xs px-2 py-1 rounded-full ${event.status === 'Confirmado' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}`}> <span className={`text-xs px-2 py-1 rounded-full ${event.status === 'Confirmado' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}`}>
{event.status} {event.status}
</span> </span>
</h1> </h1>
<p className="text-sm text-gray-500">ID: {event.fot_id}</p> {(event.fot_id || event.fot || event.id) && (
<p className="text-sm text-gray-500">ID: {event.fot_id || event.fot || event.id}</p>
)}
</div> </div>
</div> </div>

View file

@ -51,12 +51,22 @@ export async function searchMapboxLocation(
try { try {
const encodedQuery = encodeURIComponent(query); const encodedQuery = encodeURIComponent(query);
const url = let url =
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodedQuery}.json?` + `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodedQuery}.json?` +
`access_token=${MAPBOX_TOKEN}&` + `access_token=${MAPBOX_TOKEN}&` +
`country=${country}&` + `country=${country}&` +
`language=pt&` + `language=pt&` +
`limit=5`; `limit=10`;
// Add proximity bias based on region
const region = localStorage.getItem("photum_selected_region");
if (region === "MG") {
// Belo Horizonteish center
url += `&proximity=-43.9378,-19.9208`;
} else {
// São Pauloish center (Default)
url += `&proximity=-46.6333,-23.5505`;
}
console.log("🔍 Buscando endereço:", query); console.log("🔍 Buscando endereço:", query);
const response = await fetch(url); const response = await fetch(url);