From 02309f74c0f00f678e501ff37e9c0abf43e0938b Mon Sep 17 00:00:00 2001 From: NANDO9322 Date: Tue, 3 Feb 2026 17:46:52 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20Implementa=20valida=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20e-mail=20=C3=BAnico=20e=20melhorias=20na=20aprova=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20usu=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - Adiciona constraint UNIQUE para 'email' na tabela cadastro_profissionais. - Atualiza schema.sql para converter e-mails vazios para NULL automaticamente. - Modifica query CreateProfissional para usar ON CONFLICT (email) DO UPDATE (Upsert). - Ajusta helper toPgText para tratar string vazia como NULL, permitindo múltiplos profissionais sem e-mail. Frontend: - Adiciona Modal de Detalhes do Usuário na página de Aprovação. - Oculta seletor de função para usuários do tipo 'Cliente'. --- .../db/generated/profissionais.sql.go | 26 ++- backend/internal/db/queries/profissionais.sql | 26 ++- backend/internal/db/schema.sql | 11 + backend/internal/profissionais/service.go | 2 +- frontend/pages/UserApproval.tsx | 191 +++++++++++++++--- 5 files changed, 223 insertions(+), 33 deletions(-) diff --git a/backend/internal/db/generated/profissionais.sql.go b/backend/internal/db/generated/profissionais.sql.go index c0f5658..744478c 100644 --- a/backend/internal/db/generated/profissionais.sql.go +++ b/backend/internal/db/generated/profissionais.sql.go @@ -46,7 +46,31 @@ INSERT INTO cadastro_profissionais ( ) 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, $26 -) RETURNING id, usuario_id, nome, funcao_profissional_id, endereco, cidade, uf, whatsapp, cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel, tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec, educacao_simpatia, desempenho_evento, disp_horario, media, tabela_free, extra_por_equipamento, equipamentos, email, avatar_url, criado_em, atualizado_em +) +ON CONFLICT (email) DO UPDATE SET + nome = EXCLUDED.nome, + funcao_profissional_id = EXCLUDED.funcao_profissional_id, + whatsapp = EXCLUDED.whatsapp, + cpf_cnpj_titular = EXCLUDED.cpf_cnpj_titular, + banco = EXCLUDED.banco, + agencia = EXCLUDED.agencia, + conta_pix = EXCLUDED.conta_pix, + carro_disponivel = EXCLUDED.carro_disponivel, + tem_estudio = EXCLUDED.tem_estudio, + qtd_estudio = EXCLUDED.qtd_estudio, + tipo_cartao = EXCLUDED.tipo_cartao, + observacao = EXCLUDED.observacao, + qual_tec = EXCLUDED.qual_tec, + educacao_simpatia = EXCLUDED.educacao_simpatia, + desempenho_evento = EXCLUDED.desempenho_evento, + disp_horario = EXCLUDED.disp_horario, + media = EXCLUDED.media, + tabela_free = EXCLUDED.tabela_free, + extra_por_equipamento = EXCLUDED.extra_por_equipamento, + equipamentos = EXCLUDED.equipamentos, + avatar_url = EXCLUDED.avatar_url, + atualizado_em = NOW() +RETURNING id, usuario_id, nome, funcao_profissional_id, endereco, cidade, uf, whatsapp, cpf_cnpj_titular, banco, agencia, conta_pix, carro_disponivel, tem_estudio, qtd_estudio, tipo_cartao, observacao, qual_tec, educacao_simpatia, desempenho_evento, disp_horario, media, tabela_free, extra_por_equipamento, equipamentos, email, avatar_url, criado_em, atualizado_em ` type CreateProfissionalParams struct { diff --git a/backend/internal/db/queries/profissionais.sql b/backend/internal/db/queries/profissionais.sql index 49fe346..4122ce3 100644 --- a/backend/internal/db/queries/profissionais.sql +++ b/backend/internal/db/queries/profissionais.sql @@ -8,7 +8,31 @@ INSERT INTO cadastro_profissionais ( ) 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, $26 -) RETURNING *; +) +ON CONFLICT (email) DO UPDATE SET + nome = EXCLUDED.nome, + funcao_profissional_id = EXCLUDED.funcao_profissional_id, + whatsapp = EXCLUDED.whatsapp, + cpf_cnpj_titular = EXCLUDED.cpf_cnpj_titular, + banco = EXCLUDED.banco, + agencia = EXCLUDED.agencia, + conta_pix = EXCLUDED.conta_pix, + carro_disponivel = EXCLUDED.carro_disponivel, + tem_estudio = EXCLUDED.tem_estudio, + qtd_estudio = EXCLUDED.qtd_estudio, + tipo_cartao = EXCLUDED.tipo_cartao, + observacao = EXCLUDED.observacao, + qual_tec = EXCLUDED.qual_tec, + educacao_simpatia = EXCLUDED.educacao_simpatia, + desempenho_evento = EXCLUDED.desempenho_evento, + disp_horario = EXCLUDED.disp_horario, + media = EXCLUDED.media, + tabela_free = EXCLUDED.tabela_free, + extra_por_equipamento = EXCLUDED.extra_por_equipamento, + equipamentos = EXCLUDED.equipamentos, + avatar_url = EXCLUDED.avatar_url, + atualizado_em = NOW() +RETURNING *; -- name: GetProfissionalByUsuarioID :one SELECT p.*, diff --git a/backend/internal/db/schema.sql b/backend/internal/db/schema.sql index b35ad93..e9ac45c 100644 --- a/backend/internal/db/schema.sql +++ b/backend/internal/db/schema.sql @@ -501,3 +501,14 @@ BEGIN END IF; END $$; +DO $$ +BEGIN + -- 1. Converter emails vazios para NULL para evitar erro de duplicidade em strings vazias + UPDATE cadastro_profissionais SET email = NULL WHERE email = ''; + + -- 2. Aplicar Constraint Única se não existir + IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name='unique_email_profissional' AND table_name='cadastro_profissionais') THEN + ALTER TABLE cadastro_profissionais ADD CONSTRAINT unique_email_profissional UNIQUE (email); + END IF; +END $$; + diff --git a/backend/internal/profissionais/service.go b/backend/internal/profissionais/service.go index e67d080..60ba2cd 100644 --- a/backend/internal/profissionais/service.go +++ b/backend/internal/profissionais/service.go @@ -414,7 +414,7 @@ func (s *Service) Delete(ctx context.Context, id string) error { // Helpers func toPgText(s *string) pgtype.Text { - if s == nil { + if s == nil || *s == "" { return pgtype.Text{Valid: false} } return pgtype.Text{String: *s, Valid: true} diff --git a/frontend/pages/UserApproval.tsx b/frontend/pages/UserApproval.tsx index 3fef23c..8d444e9 100644 --- a/frontend/pages/UserApproval.tsx +++ b/frontend/pages/UserApproval.tsx @@ -35,6 +35,7 @@ export const UserApproval: React.FC = ({ onNavigate }) => { "cliente" ); const [isProcessing, setIsProcessing] = useState(null); + const [selectedUser, setSelectedUser] = useState(null); const fetchUsers = async () => { if (!token) { @@ -144,6 +145,134 @@ export const UserApproval: React.FC = ({ onNavigate }) => { } }; + // Modal Component + const UserDetailsModal = () => { + if (!selectedUser) return null; + + return ( +
+
+
+

+ Detalhes do Cadastro +

+ +
+ +
+
+
+ +

+ {selectedUser.name || "-"} +

+
+
+ +

+ {selectedUser.email || "-"} +

+
+ +
+ +

+ {selectedUser.phone || "-"} +

+
+
+ +

+ {selectedUser.created_at ? new Date(selectedUser.created_at).toLocaleDateString("pt-BR") : "-"} +

+
+ + {selectedUser.role === "EVENT_OWNER" && ( +
+ +

+ {selectedUser.company_name || "-"} +

+
+ )} +
+ +
+ + {selectedUser.role === "EVENT_OWNER" ? ( + + Cliente (Empresa) + + ) : ( + + )} +
+
+ +
+ + +
+
+
+ ); + }; + return (
@@ -281,7 +410,8 @@ export const UserApproval: React.FC = ({ onNavigate }) => { filteredUsers.map((user, index) => ( setSelectedUser(user)} >
@@ -306,34 +436,34 @@ export const UserApproval: React.FC = ({ onNavigate }) => {
{/* Role Editor */} - - + e.stopPropagation()}> + {activeTab === "cliente" ? ( + Cliente + ) : ( + + )}
@@ -349,7 +479,7 @@ export const UserApproval: React.FC = ({ onNavigate }) => { user.approvalStatus || UserApprovalStatus.PENDING )} - + e.stopPropagation()}>
+
);