diff --git a/backend/internal/agenda/service.go b/backend/internal/agenda/service.go index 19a39e1..e8c6e51 100644 --- a/backend/internal/agenda/service.go +++ b/backend/internal/agenda/service.go @@ -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 { - FotID uuid.UUID `json:"fot_id"` - DataEvento time.Time `json:"data_evento"` - TipoEventoID uuid.UUID `json:"tipo_evento_id"` - ObservacoesEvento string `json:"observacoes_evento"` - LocalEvento string `json:"local_evento"` - Endereco string `json:"endereco"` - Horario string `json:"horario"` - QtdFormandos int32 `json:"qtd_formandos"` - QtdFotografos int32 `json:"qtd_fotografos"` - QtdRecepcionistas int32 `json:"qtd_recepcionistas"` - QtdCinegrafistas int32 `json:"qtd_cinegrafistas"` - QtdEstudios int32 `json:"qtd_estudios"` - QtdPontoFoto int32 `json:"qtd_ponto_foto"` - QtdPontoID int32 `json:"qtd_ponto_id"` - QtdPontoDecorado int32 `json:"qtd_ponto_decorado"` - QtdPontosLed int32 `json:"qtd_pontos_led"` - QtdPlataforma360 int32 `json:"qtd_plataforma_360"` - StatusProfissionais string `json:"status_profissionais"` - FotoFaltante int32 `json:"foto_faltante"` - RecepFaltante int32 `json:"recep_faltante"` - CineFaltante int32 `json:"cine_faltante"` - LogisticaObservacoes string `json:"logistica_observacoes"` - PreVenda bool `json:"pre_venda"` + FotID uuid.UUID `json:"fot_id"` + DataEvento time.Time `json:"data_evento"` + TipoEventoID uuid.UUID `json:"tipo_evento_id"` + ObservacoesEvento string `json:"observacoes_evento"` + LocalEvento string `json:"local_evento"` + Endereco string `json:"endereco"` + Horario string `json:"horario"` + QtdFormandos int32 `json:"qtd_formandos"` + QtdFotografos int32 `json:"qtd_fotografos"` + QtdRecepcionistas int32 `json:"qtd_recepcionistas"` + QtdCinegrafistas int32 `json:"qtd_cinegrafistas"` + QtdEstudios int32 `json:"qtd_estudios"` + QtdPontoFoto int32 `json:"qtd_ponto_foto"` + QtdPontoID int32 `json:"qtd_ponto_id"` + QtdPontoDecorado int32 `json:"qtd_ponto_decorado"` + QtdPontosLed int32 `json:"qtd_pontos_led"` + QtdPlataforma360 int32 `json:"qtd_plataforma_360"` + StatusProfissionais string `json:"status_profissionais"` + FotoFaltante int32 `json:"foto_faltante"` + RecepFaltante int32 `json:"recep_faltante"` + CineFaltante int32 `json:"cine_faltante"` + LogisticaObservacoes string `json:"logistica_observacoes"` + PreVenda bool `json:"pre_venda"` + Contatos []ContactInfo `json:"contatos"` } type Assignment struct { @@ -67,7 +74,8 @@ type Assignment struct { type AgendaResponse struct { generated.ListAgendasRow - ParsedAssignments []Assignment `json:"assignments"` + ParsedAssignments []Assignment `json:"assignments"` + ParsedContacts []ContactInfo `json:"contacts"` } 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) { status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante) + contatosBytes, _ := json.Marshal(req.Contatos) + params := generated.CreateAgendaParams{ FotID: pgtype.UUID{Bytes: req.FotID, 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}, UserID: pgtype.UUID{Bytes: userID, Valid: true}, Regiao: pgtype.Text{String: regiao, Valid: true}, + Contatos: contatosBytes, } 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, TipoEventoNome: r.TipoEventoNome, AssignedProfessionals: r.AssignedProfessionals, + Contatos: r.Contatos, }) } } else { @@ -207,9 +219,16 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string, regia json.Unmarshal(bytes, &assignments) } } + + var contacts []ContactInfo + if len(row.Contatos) > 0 { + json.Unmarshal(row.Contatos, &contacts) + } + response = append(response, AgendaResponse{ ListAgendasRow: row, 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) + contatosBytes, _ := json.Marshal(req.Contatos) + params := generated.UpdateAgendaParams{ ID: pgtype.UUID{Bytes: id, 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 != ""}, PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true}, Regiao: pgtype.Text{String: regiao, Valid: true}, + Contatos: contatosBytes, } return s.queries.UpdateAgenda(ctx, params) } diff --git a/backend/internal/db/generated/agenda.sql.go b/backend/internal/db/generated/agenda.sql.go index ad3470f..c007544 100644 --- a/backend/internal/db/generated/agenda.sql.go +++ b/backend/internal/db/generated/agenda.sql.go @@ -97,10 +97,11 @@ INSERT INTO agenda ( logistica_observacoes, pre_venda, user_id, - regiao + regiao, + contatos ) 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 -) 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 + $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, contatos ` type CreateAgendaParams struct { @@ -128,6 +129,7 @@ type CreateAgendaParams struct { LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` PreVenda pgtype.Bool `json:"pre_venda"` UserID pgtype.UUID `json:"user_id"` + Contatos []byte `json:"contatos"` Regiao pgtype.Text `json:"regiao"` } @@ -157,6 +159,7 @@ func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Age arg.LogisticaObservacoes, arg.PreVenda, arg.UserID, + arg.Contatos, arg.Regiao, ) var i Agenda @@ -191,6 +194,7 @@ func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Age &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, ) return i, err } @@ -211,7 +215,7 @@ func (q *Queries) DeleteAgenda(ctx context.Context, arg DeleteAgendaParams) erro } 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 ` @@ -254,12 +258,13 @@ func (q *Queries) GetAgenda(ctx context.Context, arg GetAgendaParams) (Agenda, e &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, ) return i, err } 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 LIMIT 1 ` @@ -310,6 +315,7 @@ func (q *Queries) GetAgendaByFotDataTipo(ctx context.Context, arg GetAgendaByFot &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, ) return i, err } @@ -413,7 +419,7 @@ func (q *Queries) GetAgendaProfessionals(ctx context.Context, agendaID pgtype.UU const listAgendas = `-- name: ListAgendas :many 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.instituicao, c.nome as curso_nome, @@ -474,6 +480,7 @@ type ListAgendasRow struct { Status pgtype.Text `json:"status"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` Regiao pgtype.Text `json:"regiao"` + Contatos []byte `json:"contatos"` FotNumero string `json:"fot_numero"` Instituicao pgtype.Text `json:"instituicao"` CursoNome string `json:"curso_nome"` @@ -525,6 +532,7 @@ func (q *Queries) ListAgendas(ctx context.Context, regiao pgtype.Text) ([]ListAg &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, &i.FotNumero, &i.Instituicao, &i.CursoNome, @@ -547,7 +555,7 @@ func (q *Queries) ListAgendas(ctx context.Context, regiao pgtype.Text) ([]ListAg const listAgendasByCompany = `-- name: ListAgendasByCompany :many 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.instituicao, c.nome as curso_nome, @@ -613,6 +621,7 @@ type ListAgendasByCompanyRow struct { Status pgtype.Text `json:"status"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` Regiao pgtype.Text `json:"regiao"` + Contatos []byte `json:"contatos"` FotNumero string `json:"fot_numero"` Instituicao pgtype.Text `json:"instituicao"` CursoNome string `json:"curso_nome"` @@ -664,6 +673,7 @@ func (q *Queries) ListAgendasByCompany(ctx context.Context, arg ListAgendasByCom &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, &i.FotNumero, &i.Instituicao, &i.CursoNome, @@ -686,7 +696,7 @@ func (q *Queries) ListAgendasByCompany(ctx context.Context, arg ListAgendasByCom const listAgendasByFot = `-- name: ListAgendasByFot :many 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 FROM agenda a JOIN tipos_eventos te ON a.tipo_evento_id = te.id @@ -730,6 +740,7 @@ type ListAgendasByFotRow struct { Status pgtype.Text `json:"status"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` Regiao pgtype.Text `json:"regiao"` + Contatos []byte `json:"contatos"` TipoEventoNome string `json:"tipo_evento_nome"` } @@ -773,6 +784,7 @@ func (q *Queries) ListAgendasByFot(ctx context.Context, arg ListAgendasByFotPara &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, &i.TipoEventoNome, ); err != nil { return nil, err @@ -787,7 +799,7 @@ func (q *Queries) ListAgendasByFot(ctx context.Context, arg ListAgendasByFotPara const listAgendasByUser = `-- name: ListAgendasByUser :many 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.instituicao, c.nome as curso_nome, @@ -853,6 +865,7 @@ type ListAgendasByUserRow struct { Status pgtype.Text `json:"status"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` Regiao pgtype.Text `json:"regiao"` + Contatos []byte `json:"contatos"` FotNumero string `json:"fot_numero"` Instituicao pgtype.Text `json:"instituicao"` CursoNome string `json:"curso_nome"` @@ -904,6 +917,7 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, arg ListAgendasByUserPa &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, &i.FotNumero, &i.Instituicao, &i.CursoNome, @@ -1083,9 +1097,10 @@ SET cine_faltante = $22, logistica_observacoes = $23, pre_venda = $24, + contatos = $25, atualizado_em = NOW() -WHERE id = $1 AND regiao = $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 +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, contatos ` type UpdateAgendaParams struct { @@ -1113,6 +1128,7 @@ type UpdateAgendaParams struct { CineFaltante pgtype.Int4 `json:"cine_faltante"` LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"` PreVenda pgtype.Bool `json:"pre_venda"` + Contatos []byte `json:"contatos"` Regiao pgtype.Text `json:"regiao"` } @@ -1142,6 +1158,7 @@ func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Age arg.CineFaltante, arg.LogisticaObservacoes, arg.PreVenda, + arg.Contatos, arg.Regiao, ) var i Agenda @@ -1176,6 +1193,7 @@ func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Age &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, ) return i, err } @@ -1184,7 +1202,7 @@ const updateAgendaStatus = `-- name: UpdateAgendaStatus :one UPDATE agenda SET status = $2, atualizado_em = NOW() 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 { @@ -1227,6 +1245,7 @@ func (q *Queries) UpdateAgendaStatus(ctx context.Context, arg UpdateAgendaStatus &i.Status, &i.LogisticaNotificacaoEnviadaEm, &i.Regiao, + &i.Contatos, ) return i, err } diff --git a/backend/internal/db/generated/models.go b/backend/internal/db/generated/models.go index 1ac9ef4..95e8f21 100644 --- a/backend/internal/db/generated/models.go +++ b/backend/internal/db/generated/models.go @@ -39,6 +39,7 @@ type Agenda struct { Status pgtype.Text `json:"status"` LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"` Regiao pgtype.Text `json:"regiao"` + Contatos []byte `json:"contatos"` } type AgendaEscala struct { diff --git a/backend/internal/db/migrations/016_add_contacts_to_agenda.up.sql b/backend/internal/db/migrations/016_add_contacts_to_agenda.up.sql new file mode 100644 index 0000000..9d08161 --- /dev/null +++ b/backend/internal/db/migrations/016_add_contacts_to_agenda.up.sql @@ -0,0 +1 @@ +ALTER TABLE agenda ADD COLUMN IF NOT EXISTS contatos JSONB DEFAULT '[]'::jsonb; diff --git a/backend/internal/db/queries/agenda.sql b/backend/internal/db/queries/agenda.sql index a16fc4b..9c3948c 100644 --- a/backend/internal/db/queries/agenda.sql +++ b/backend/internal/db/queries/agenda.sql @@ -24,9 +24,10 @@ INSERT INTO agenda ( logistica_observacoes, pre_venda, user_id, - regiao + regiao, + contatos ) 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 *; -- name: GetAgenda :one @@ -121,6 +122,7 @@ SET cine_faltante = $22, logistica_observacoes = $23, pre_venda = $24, + contatos = $25, atualizado_em = NOW() WHERE id = $1 AND regiao = @regiao RETURNING *; diff --git a/backend/internal/db/schema.sql b/backend/internal/db/schema.sql index 842461f..ff15e19 100644 --- a/backend/internal/db/schema.sql +++ b/backend/internal/db/schema.sql @@ -231,7 +231,8 @@ CREATE TABLE IF NOT EXISTS agenda ( atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), status VARCHAR(50) DEFAULT 'Pendente', -- Pendente, Aprovado, Arquivado 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 ( diff --git a/frontend/components/EventForm.tsx b/frontend/components/EventForm.tsx index 533ed42..07b349b 100644 --- a/frontend/components/EventForm.tsx +++ b/frontend/components/EventForm.tsx @@ -398,8 +398,8 @@ export const EventForm: React.FC = ({ }; const handleSubmit = async () => { - // Validation - if (!formData.name) return alert("Preencha o tipo de evento"); + // Validation (Observations are optional) + // if (!formData.name) return alert("Preencha o tipo de evento"); if (!formData.date) return alert("Preencha a data"); if (!formData.attendees || parseInt(formData.attendees) <= 0) return alert("Preencha o número de formandos"); @@ -455,7 +455,8 @@ export const EventForm: React.FC = ({ status_profissionais: "PENDING", cine_faltante: 0, logistica_observacoes: "", - pre_venda: true + pre_venda: true, + contatos: formData.contacts }; // Submit to parent handler @@ -1124,7 +1125,7 @@ export const EventForm: React.FC = ({ className="w-full border border-gray-300 rounded-sm p-3 focus:outline-none focus:border-brand-gold h-32 text-sm" placeholder={ isClientRequest - ? "Qual o estilo do casamento? Quais fotos são indispensáveis? Fale um pouco sobre vocês..." + ? "Fale sobre..." : "Instruções técnicas..." } value={formData.briefing} @@ -1169,6 +1170,16 @@ export const EventForm: React.FC = ({ setFormData({ ...formData, contacts: newContacts }); }} /> + { + const newContacts = [...formData.contacts]; + newContacts[idx].phone = e.target.value; + setFormData({ ...formData, contacts: newContacts }); + }} + /> {/* Add Car Form - Only for Admins */} diff --git a/frontend/components/EventTable.tsx b/frontend/components/EventTable.tsx index 0d4e12c..92ddbf0 100644 --- a/frontend/components/EventTable.tsx +++ b/frontend/components/EventTable.tsx @@ -151,8 +151,11 @@ export const EventTable: React.FC = ({ bValue = (b as any).fotId || ""; break; case "date": - aValue = new Date(a.date + "T00:00:00").getTime(); - bValue = new Date(b.date + "T00:00:00").getTime(); + // Parse date robustly + 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; case "curso": aValue = ((a as any).curso || "").toLowerCase(); diff --git a/frontend/components/Navbar.tsx b/frontend/components/Navbar.tsx index e196485..4af6c98 100644 --- a/frontend/components/Navbar.tsx +++ b/frontend/components/Navbar.tsx @@ -157,7 +157,7 @@ export const Navbar: React.FC = ({ onNavigate, currentPage }) => { {/* Logo */}
navigate("/painel")} + onClick={() => window.location.href = "/painel"} > = ({ // For now, trust DataContext + local state update. // 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". - window.location.reload(); + // setView("details"); // Already called above + // removing window.location.reload() to maintain SPA feel } else { console.error("Update function not available"); } @@ -89,11 +90,33 @@ export const Dashboard: React.FC = ({ setView("list"); } }; - const [view, setView] = useState<"list" | "create" | "edit" | "details">( - initialView - ); + const [view, setView] = useState<"list" | "create" | "edit" | "details">(() => { + const params = new URLSearchParams(window.location.search); + return params.get("eventId") ? "details" : initialView; + }); const [searchTerm, setSearchTerm] = useState(""); - const [selectedEvent, setSelectedEvent] = useState(null); + const [selectedEvent, setSelectedEvent] = useState(() => { + 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("all"); const [advancedFilters, setAdvancedFilters] = useState({ date: "", @@ -331,7 +354,16 @@ export const Dashboard: React.FC = ({ }; }, [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 matchesSearch = e.name .toLowerCase() @@ -490,6 +522,17 @@ export const Dashboard: React.FC = ({ ); }; + 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 = () => { setIsTeamModalOpen(true); }; @@ -594,7 +637,7 @@ export const Dashboard: React.FC = ({
{/* Header */} - {view === "list" && ( + {view === "list" && !new URLSearchParams(window.location.search).get("eventId") && (
{renderRoleSpecificHeader()} {renderRoleSpecificActions()} @@ -602,7 +645,7 @@ export const Dashboard: React.FC = ({ )} {/* Content Switcher */} - {view === "list" && ( + {view === "list" && !new URLSearchParams(window.location.search).get("eventId") && (
{/* Search Bar */}
@@ -688,6 +731,15 @@ export const Dashboard: React.FC = ({ /> )} + + {/* Loading State for Deep Link */ } + {(!!new URLSearchParams(window.location.search).get("eventId") && (!selectedEvent || view !== "details")) && ( +
+
+

Carregando detalhes do evento...

+
+ )} + {view === "details" && selectedEvent && (
+ {/* Botão de Aprovação (Apenas Admin/Owner se pendente) */} + {(user.role === UserRole.BUSINESS_OWNER || user.role === UserRole.SUPERADMIN) && + selectedEvent.status === EventStatus.PENDING_APPROVAL && ( + + )} +

- {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}`; + })()} {event.status}

-

ID: {event.fot_id}

+ {(event.fot_id || event.fot || event.id) && ( +

ID: {event.fot_id || event.fot || event.id}

+ )}
diff --git a/frontend/services/mapboxService.ts b/frontend/services/mapboxService.ts index 014d304..58bf39a 100644 --- a/frontend/services/mapboxService.ts +++ b/frontend/services/mapboxService.ts @@ -51,12 +51,22 @@ export async function searchMapboxLocation( try { const encodedQuery = encodeURIComponent(query); - const url = + let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodedQuery}.json?` + `access_token=${MAPBOX_TOKEN}&` + `country=${country}&` + `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); const response = await fetch(url);