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:
parent
e86bd0a570
commit
21987d221e
13 changed files with 208 additions and 61 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE agenda ADD COLUMN IF NOT EXISTS contatos JSONB DEFAULT '[]'::jsonb;
|
||||||
|
|
@ -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 *;
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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" : ""
|
||||||
|
|
|
||||||
|
|
@ -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 */}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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={() => {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue