Fix: Visibilidade da agenda para clientes e correções no filtro de códigos de acesso

- Backend: Implementada query `ListAgendasByCompany` e ajustada lógica do serviço de agenda para filtrar eventos pela empresa do usuário.
- Backend: Adicionada migração segura (idempotente) para incluir coluna `empresa_id` em produção.
- Frontend: Corrigido filtro [getEventsByRole] para exibir eventos importados (da empresa) para o cliente.
- Frontend: Renomeada aba de aprovação para 'Cadastros Clientes'.
This commit is contained in:
NANDO9322 2026-02-03 12:47:43 -03:00
parent a35c8c27fa
commit ec2d96333f
5 changed files with 184 additions and 6 deletions

View file

@ -122,13 +122,24 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string) ([]Ag
var rows []generated.ListAgendasRow
var err error
// If role is CLIENT (cliente), filter by userID
// If role is CLIENT (cliente) or EVENT_OWNER
if role == "cliente" || role == "EVENT_OWNER" {
listRows, err := s.queries.ListAgendasByUser(ctx, pgtype.UUID{Bytes: userID, Valid: true})
// New Logic: Fetch User's Company
user, err := s.queries.GetUsuarioByID(ctx, pgtype.UUID{Bytes: userID, Valid: true})
if err != nil {
return nil, fmt.Errorf("erro ao buscar usuário para filtro de empresa: %v", err)
}
if !user.EmpresaID.Valid {
// If no company linked, return empty or error? Empty seems safer.
return []AgendaResponse{}, nil
}
listRows, err := s.queries.ListAgendasByCompany(ctx, user.EmpresaID)
if err != nil {
return nil, err
}
// Convert ListAgendasByUserRow to ListAgendasRow manually
// Convert ListAgendasByCompanyRow to ListAgendasRow manually
for _, r := range listRows {
rows = append(rows, generated.ListAgendasRow{
ID: r.ID,

View file

@ -518,6 +518,138 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) {
return items, nil
}
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,
cf.fot as fot_numero,
cf.instituicao,
c.nome as curso_nome,
e.nome as empresa_nome,
af.ano_semestre,
cf.observacoes as observacoes_fot,
te.nome as tipo_evento_nome,
cf.empresa_id,
COALESCE(
(SELECT json_agg(json_build_object(
'professional_id', ap.profissional_id,
'status', ap.status,
'motivo_rejeicao', ap.motivo_rejeicao,
'funcao_id', ap.funcao_id
))
FROM agenda_profissionais ap
WHERE ap.agenda_id = a.id),
'[]'::json
) as assigned_professionals
FROM agenda a
JOIN cadastro_fot cf ON a.fot_id = cf.id
JOIN cursos c ON cf.curso_id = c.id
JOIN empresas e ON cf.empresa_id = e.id
JOIN anos_formaturas af ON cf.ano_formatura_id = af.id
JOIN tipos_eventos te ON a.tipo_evento_id = te.id
WHERE cf.empresa_id = $1
ORDER BY a.data_evento
`
type ListAgendasByCompanyRow struct {
ID pgtype.UUID `json:"id"`
UserID pgtype.UUID `json:"user_id"`
FotID pgtype.UUID `json:"fot_id"`
DataEvento pgtype.Date `json:"data_evento"`
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
LocalEvento pgtype.Text `json:"local_evento"`
Endereco pgtype.Text `json:"endereco"`
Horario pgtype.Text `json:"horario"`
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
StatusProfissionais pgtype.Text `json:"status_profissionais"`
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
CineFaltante pgtype.Int4 `json:"cine_faltante"`
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
PreVenda pgtype.Bool `json:"pre_venda"`
CriadoEm pgtype.Timestamptz `json:"criado_em"`
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
Status pgtype.Text `json:"status"`
LogisticaNotificacaoEnviadaEm pgtype.Timestamp `json:"logistica_notificacao_enviada_em"`
FotNumero string `json:"fot_numero"`
Instituicao pgtype.Text `json:"instituicao"`
CursoNome string `json:"curso_nome"`
EmpresaNome string `json:"empresa_nome"`
AnoSemestre string `json:"ano_semestre"`
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
TipoEventoNome string `json:"tipo_evento_nome"`
EmpresaID pgtype.UUID `json:"empresa_id"`
AssignedProfessionals interface{} `json:"assigned_professionals"`
}
func (q *Queries) ListAgendasByCompany(ctx context.Context, empresaID pgtype.UUID) ([]ListAgendasByCompanyRow, error) {
rows, err := q.db.Query(ctx, listAgendasByCompany, empresaID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAgendasByCompanyRow
for rows.Next() {
var i ListAgendasByCompanyRow
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.FotID,
&i.DataEvento,
&i.TipoEventoID,
&i.ObservacoesEvento,
&i.LocalEvento,
&i.Endereco,
&i.Horario,
&i.QtdFormandos,
&i.QtdFotografos,
&i.QtdRecepcionistas,
&i.QtdCinegrafistas,
&i.QtdEstudios,
&i.QtdPontoFoto,
&i.QtdPontoID,
&i.QtdPontoDecorado,
&i.QtdPontosLed,
&i.QtdPlataforma360,
&i.StatusProfissionais,
&i.FotoFaltante,
&i.RecepFaltante,
&i.CineFaltante,
&i.LogisticaObservacoes,
&i.PreVenda,
&i.CriadoEm,
&i.AtualizadoEm,
&i.Status,
&i.LogisticaNotificacaoEnviadaEm,
&i.FotNumero,
&i.Instituicao,
&i.CursoNome,
&i.EmpresaNome,
&i.AnoSemestre,
&i.ObservacoesFot,
&i.TipoEventoNome,
&i.EmpresaID,
&i.AssignedProfessionals,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
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,

View file

@ -205,4 +205,35 @@ ORDER BY a.data_evento;
-- name: GetAgendaByFotDataTipo :one
SELECT * FROM agenda
WHERE fot_id = $1 AND data_evento = $2 AND tipo_evento_id = $3
LIMIT 1;
LIMIT 1;
-- name: ListAgendasByCompany :many
SELECT
a.*,
cf.fot as fot_numero,
cf.instituicao,
c.nome as curso_nome,
e.nome as empresa_nome,
af.ano_semestre,
cf.observacoes as observacoes_fot,
te.nome as tipo_evento_nome,
cf.empresa_id,
COALESCE(
(SELECT json_agg(json_build_object(
'professional_id', ap.profissional_id,
'status', ap.status,
'motivo_rejeicao', ap.motivo_rejeicao,
'funcao_id', ap.funcao_id
))
FROM agenda_profissionais ap
WHERE ap.agenda_id = a.id),
'[]'::json
) as assigned_professionals
FROM agenda a
JOIN cadastro_fot cf ON a.fot_id = cf.id
JOIN cursos c ON cf.curso_id = c.id
JOIN empresas e ON cf.empresa_id = e.id
JOIN anos_formaturas af ON cf.ano_formatura_id = af.id
JOIN tipos_eventos te ON a.tipo_evento_id = te.id
WHERE cf.empresa_id = $1
ORDER BY a.data_evento;

View file

@ -976,6 +976,10 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
return events;
}
if (role === "EVENT_OWNER") {
// Check if logged user has company linked and matches requested user
if (user && user.id === userId && user.empresaId) {
return events.filter(e => e.empresaId === user.empresaId);
}
return events.filter((e) => e.ownerId === userId);
}
if (role === "PHOTOGRAPHER") {

View file

@ -168,7 +168,7 @@ export const UserApproval: React.FC<UserApprovalProps> = ({ onNavigate }) => {
}`}
>
<Users className="w-5 h-5" />
Cadastros Empresas
Cadastros Clientes
<span
className={`ml-2 py-0.5 px-2.5 rounded-full text-xs ${activeTab === "cliente"
? "bg-[#B9CF33] text-white"
@ -271,7 +271,7 @@ export const UserApproval: React.FC<UserApprovalProps> = ({ onNavigate }) => {
)}
<p className="text-lg font-medium">
{activeTab === "cliente"
? "Nenhum cadastro de empresa encontrado"
? "Nenhum cadastro de cliente encontrado"
: "Nenhum cadastro profissional encontrado"}
</p>
</div>