Merge pull request #49 from rede5/hml

Hml
This commit is contained in:
Andre F. Rodrigues 2026-01-29 22:18:04 -03:00 committed by GitHub
commit 14880f682f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 310 additions and 234 deletions

View file

@ -673,9 +673,9 @@ func (s *Service) GetProfessionalFinancialStatement(ctx context.Context, userID
nomeEvento := "" nomeEvento := ""
if t.FotNumero.Valid { if t.FotNumero.Valid {
if t.CursoNome.Valid { if t.CursoNome.Valid {
nomeEvento = fmt.Sprintf("Formatura %s (FOT %d)", t.CursoNome.String, t.FotNumero.Int32) nomeEvento = fmt.Sprintf("Formatura %s (FOT %s)", t.CursoNome.String, t.FotNumero.String)
} else { } else {
nomeEvento = fmt.Sprintf("Formatura FOT %d", t.FotNumero.Int32) nomeEvento = fmt.Sprintf("Formatura FOT %s", t.FotNumero.String)
} }
} else { } else {
nomeEvento = t.TipoEvento.String nomeEvento = t.TipoEvento.String

View file

@ -20,7 +20,7 @@ func NewHandler(service *Service) *Handler {
type CadastroFotResponse struct { type CadastroFotResponse struct {
ID string `json:"id"` ID string `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID string `json:"empresa_id"` EmpresaID string `json:"empresa_id"`
EmpresaNome string `json:"empresa_nome,omitempty"` EmpresaNome string `json:"empresa_nome,omitempty"`
CursoID string `json:"curso_id"` CursoID string `json:"curso_id"`

View file

@ -20,7 +20,7 @@ func NewService(queries *generated.Queries) *Service {
} }
type CreateInput struct { type CreateInput struct {
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID string `json:"empresa_id"` EmpresaID string `json:"empresa_id"`
CursoID string `json:"curso_id"` CursoID string `json:"curso_id"`
AnoFormaturaID string `json:"ano_formatura_id"` AnoFormaturaID string `json:"ano_formatura_id"`

View file

@ -396,7 +396,7 @@ type ListAgendasRow struct {
CriadoEm pgtype.Timestamptz `json:"criado_em"` CriadoEm pgtype.Timestamptz `json:"criado_em"`
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
FotNumero int32 `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"`
EmpresaNome string `json:"empresa_nome"` EmpresaNome string `json:"empresa_nome"`
@ -618,7 +618,7 @@ type ListAgendasByUserRow struct {
CriadoEm pgtype.Timestamptz `json:"criado_em"` CriadoEm pgtype.Timestamptz `json:"criado_em"`
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
FotNumero int32 `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"`
EmpresaNome string `json:"empresa_nome"` EmpresaNome string `json:"empresa_nome"`

View file

@ -20,7 +20,7 @@ INSERT INTO cadastro_fot (
` `
type CreateCadastroFotParams struct { type CreateCadastroFotParams struct {
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`
@ -77,7 +77,7 @@ const getCadastroFotByFOT = `-- name: GetCadastroFotByFOT :one
SELECT id, fot, empresa_id, curso_id, ano_formatura_id, instituicao, cidade, estado, observacoes, gastos_captacao, pre_venda, created_at, updated_at FROM cadastro_fot WHERE fot = $1 SELECT id, fot, empresa_id, curso_id, ano_formatura_id, instituicao, cidade, estado, observacoes, gastos_captacao, pre_venda, created_at, updated_at FROM cadastro_fot WHERE fot = $1
` `
func (q *Queries) GetCadastroFotByFOT(ctx context.Context, fot int32) (CadastroFot, error) { func (q *Queries) GetCadastroFotByFOT(ctx context.Context, fot string) (CadastroFot, error) {
row := q.db.QueryRow(ctx, getCadastroFotByFOT, fot) row := q.db.QueryRow(ctx, getCadastroFotByFOT, fot)
var i CadastroFot var i CadastroFot
err := row.Scan( err := row.Scan(
@ -113,7 +113,7 @@ WHERE c.fot = $1
type GetCadastroFotByFotJoinRow struct { type GetCadastroFotByFotJoinRow struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`
@ -130,7 +130,7 @@ type GetCadastroFotByFotJoinRow struct {
AnoFormaturaLabel string `json:"ano_formatura_label"` AnoFormaturaLabel string `json:"ano_formatura_label"`
} }
func (q *Queries) GetCadastroFotByFotJoin(ctx context.Context, fot int32) (GetCadastroFotByFotJoinRow, error) { func (q *Queries) GetCadastroFotByFotJoin(ctx context.Context, fot string) (GetCadastroFotByFotJoinRow, error) {
row := q.db.QueryRow(ctx, getCadastroFotByFotJoin, fot) row := q.db.QueryRow(ctx, getCadastroFotByFotJoin, fot)
var i GetCadastroFotByFotJoinRow var i GetCadastroFotByFotJoinRow
err := row.Scan( err := row.Scan(
@ -169,7 +169,7 @@ WHERE c.id = $1
type GetCadastroFotByIDRow struct { type GetCadastroFotByIDRow struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`
@ -225,7 +225,7 @@ ORDER BY c.fot DESC
type ListCadastroFotRow struct { type ListCadastroFotRow struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`
@ -295,7 +295,7 @@ ORDER BY c.fot DESC
type ListCadastroFotByEmpresaRow struct { type ListCadastroFotByEmpresaRow struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`
@ -366,7 +366,7 @@ LIMIT 10
type SearchFotRow struct { type SearchFotRow struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`
@ -439,7 +439,7 @@ RETURNING id, fot, empresa_id, curso_id, ano_formatura_id, instituicao, cidade,
type UpdateCadastroFotParams struct { type UpdateCadastroFotParams struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`

View file

@ -144,7 +144,7 @@ type ListTransactionsRow struct {
CriadoEm pgtype.Timestamptz `json:"criado_em"` CriadoEm pgtype.Timestamptz `json:"criado_em"`
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
ProfissionalID pgtype.UUID `json:"profissional_id"` ProfissionalID pgtype.UUID `json:"profissional_id"`
FotNumero pgtype.Int4 `json:"fot_numero"` FotNumero pgtype.Text `json:"fot_numero"`
} }
func (q *Queries) ListTransactions(ctx context.Context) ([]ListTransactionsRow, error) { func (q *Queries) ListTransactions(ctx context.Context) ([]ListTransactionsRow, error) {
@ -271,7 +271,7 @@ type ListTransactionsByProfessionalRow struct {
CriadoEm pgtype.Timestamptz `json:"criado_em"` CriadoEm pgtype.Timestamptz `json:"criado_em"`
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
ProfissionalID pgtype.UUID `json:"profissional_id"` ProfissionalID pgtype.UUID `json:"profissional_id"`
FotNumero pgtype.Int4 `json:"fot_numero"` FotNumero pgtype.Text `json:"fot_numero"`
EmpresaNome pgtype.Text `json:"empresa_nome"` EmpresaNome pgtype.Text `json:"empresa_nome"`
CursoNome pgtype.Text `json:"curso_nome"` CursoNome pgtype.Text `json:"curso_nome"`
} }

View file

@ -79,7 +79,7 @@ type CadastroCliente struct {
type CadastroFot struct { type CadastroFot struct {
ID pgtype.UUID `json:"id"` ID pgtype.UUID `json:"id"`
Fot int32 `json:"fot"` Fot string `json:"fot"`
EmpresaID pgtype.UUID `json:"empresa_id"` EmpresaID pgtype.UUID `json:"empresa_id"`
CursoID pgtype.UUID `json:"curso_id"` CursoID pgtype.UUID `json:"curso_id"`
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"` AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`

View file

@ -0,0 +1,207 @@
-- Migration de Sincronização Completa (DEV -> PROD/HML)
-- Este script verifica e cria colunas/tabelas que podem estar faltando após o merge de DEV.
-- Seguro para rodar múltiplas vezes (usa IF NOT EXISTS).
-- ==============================================================================
-- 1. UTILS
-- ==============================================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ==============================================================================
-- 2. CADASTRO DE PROFISSIONAIS (Atualizações de Colunas)
-- ==============================================================================
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS email VARCHAR(255);
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS avatar_url VARCHAR(255);
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS tabela_free VARCHAR(50);
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS media NUMERIC(3,2);
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS equipamentos TEXT;
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS extra_por_equipamento BOOLEAN DEFAULT FALSE;
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS qtd_estudio INT;
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS disp_horario INT;
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS desempenho_evento INT;
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS educacao_simpatia INT;
ALTER TABLE cadastro_profissionais ADD COLUMN IF NOT EXISTS qual_tec INT;
-- ==============================================================================
-- 3. TABELAS DE DOMÍNIO E CONFIGURAÇÃO
-- ==============================================================================
-- Tabela de Preços (Se não existir)
CREATE TABLE IF NOT EXISTS precos_tipos_eventos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tipo_evento_id UUID REFERENCES tipos_eventos(id) ON DELETE CASCADE,
funcao_profissional_id UUID REFERENCES funcoes_profissionais(id) ON DELETE CASCADE,
valor NUMERIC(10,2) NOT NULL DEFAULT 0.00,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(tipo_evento_id, funcao_profissional_id)
);
-- Tabela de Cadastro FOT (Se não existir)
CREATE TABLE IF NOT EXISTS cadastro_fot (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
fot INTEGER NOT NULL UNIQUE,
empresa_id UUID NOT NULL REFERENCES empresas(id),
curso_id UUID NOT NULL REFERENCES cursos(id),
ano_formatura_id UUID NOT NULL REFERENCES anos_formaturas(id),
instituicao VARCHAR(255),
cidade VARCHAR(255),
estado VARCHAR(2),
observacoes TEXT,
gastos_captacao NUMERIC(10, 2),
pre_venda BOOLEAN,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Códigos de Acesso
CREATE TABLE IF NOT EXISTS codigos_acesso (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
codigo VARCHAR(50) UNIQUE NOT NULL,
descricao VARCHAR(255),
validade_dias INT NOT NULL DEFAULT 30,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expira_em TIMESTAMPTZ NOT NULL,
ativo BOOLEAN NOT NULL DEFAULT TRUE,
usos INT NOT NULL DEFAULT 0
);
-- Cadastro Clientes
CREATE TABLE IF NOT EXISTS cadastro_clientes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
usuario_id UUID REFERENCES usuarios(id) ON DELETE CASCADE,
empresa_id UUID REFERENCES empresas(id) ON DELETE SET NULL,
nome VARCHAR(255),
telefone VARCHAR(20),
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(usuario_id)
);
-- Juntação Profissionais <-> Funções
CREATE TABLE IF NOT EXISTS profissionais_funcoes_junction (
profissional_id UUID NOT NULL REFERENCES cadastro_profissionais(id) ON DELETE CASCADE,
funcao_id UUID NOT NULL REFERENCES funcoes_profissionais(id) ON DELETE CASCADE,
PRIMARY KEY (profissional_id, funcao_id)
);
-- ==============================================================================
-- 4. MÓDULO DE AGENDA E LOGÍSTICA
-- ==============================================================================
-- Agenda
CREATE TABLE IF NOT EXISTS agenda (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES usuarios(id) ON DELETE CASCADE,
fot_id UUID NOT NULL REFERENCES cadastro_fot(id) ON DELETE CASCADE,
data_evento DATE NOT NULL,
tipo_evento_id UUID NOT NULL REFERENCES tipos_eventos(id),
observacoes_evento TEXT,
local_evento VARCHAR(255),
endereco VARCHAR(255),
horario VARCHAR(20),
qtd_formandos INTEGER DEFAULT 0,
qtd_fotografos INTEGER DEFAULT 0,
qtd_recepcionistas INTEGER DEFAULT 0,
qtd_cinegrafistas INTEGER DEFAULT 0,
qtd_estudios INTEGER DEFAULT 0,
qtd_ponto_foto INTEGER DEFAULT 0,
qtd_ponto_id INTEGER DEFAULT 0,
qtd_ponto_decorado INTEGER DEFAULT 0,
qtd_pontos_led INTEGER DEFAULT 0,
qtd_plataforma_360 INTEGER DEFAULT 0,
status_profissionais VARCHAR(20) DEFAULT 'OK',
foto_faltante INTEGER DEFAULT 0,
recep_faltante INTEGER DEFAULT 0,
cine_faltante INTEGER DEFAULT 0,
logistica_observacoes TEXT,
pre_venda BOOLEAN DEFAULT FALSE,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status VARCHAR(50) DEFAULT 'Pendente'
);
-- Agenda Profissionais (Atribuição)
CREATE TABLE IF NOT EXISTS agenda_profissionais (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
agenda_id UUID NOT NULL REFERENCES agenda(id) ON DELETE CASCADE,
profissional_id UUID NOT NULL REFERENCES cadastro_profissionais(id) ON DELETE CASCADE,
status VARCHAR(20) DEFAULT 'PENDENTE',
motivo_rejeicao TEXT,
funcao_id UUID REFERENCES funcoes_profissionais(id),
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(agenda_id, profissional_id)
);
ALTER TABLE agenda_profissionais ADD COLUMN IF NOT EXISTS posicao VARCHAR(100);
-- Disponibilidade
CREATE TABLE IF NOT EXISTS disponibilidade_profissionais (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
usuario_id UUID NOT NULL REFERENCES usuarios(id) ON DELETE CASCADE,
data DATE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'DISPONIVEL',
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(usuario_id, data)
);
-- Escalas (Time blocks)
CREATE TABLE IF NOT EXISTS agenda_escalas (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
agenda_id UUID NOT NULL REFERENCES agenda(id) ON DELETE CASCADE,
profissional_id UUID NOT NULL REFERENCES cadastro_profissionais(id) ON DELETE CASCADE,
data_hora_inicio TIMESTAMPTZ NOT NULL,
data_hora_fim TIMESTAMPTZ NOT NULL,
funcao_especifica VARCHAR(100),
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Mapas
CREATE TABLE IF NOT EXISTS mapas_eventos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
agenda_id UUID NOT NULL REFERENCES agenda(id) ON DELETE CASCADE,
nome VARCHAR(100),
imagem_url TEXT,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Marcadores
CREATE TABLE IF NOT EXISTS marcadores_mapa (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
mapa_id UUID NOT NULL REFERENCES mapas_eventos(id) ON DELETE CASCADE,
profissional_id UUID REFERENCES cadastro_profissionais(id) ON DELETE CASCADE,
pos_x NUMERIC(5,2),
pos_y NUMERIC(5,2),
rotulo VARCHAR(50),
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Logística Carros
CREATE TABLE IF NOT EXISTS logistica_carros (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
agenda_id UUID NOT NULL REFERENCES agenda(id) ON DELETE CASCADE,
motorista_id UUID REFERENCES cadastro_profissionais(id) ON DELETE SET NULL,
nome_motorista VARCHAR(255),
horario_chegada VARCHAR(20),
observacoes TEXT,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Logística Passageiros
CREATE TABLE IF NOT EXISTS logistica_passageiros (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
carro_id UUID NOT NULL REFERENCES logistica_carros(id) ON DELETE CASCADE,
profissional_id UUID NOT NULL REFERENCES cadastro_profissionais(id) ON DELETE CASCADE,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(carro_id, profissional_id)
);
-- ==============================================================================
-- 5. MÓDULO FINANCEIRO (Atualizações)
-- ==============================================================================
DO $$
BEGIN
IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'financial_transactions') THEN
ALTER TABLE financial_transactions ADD COLUMN IF NOT EXISTS profissional_id UUID REFERENCES cadastro_profissionais(id);
END IF;
END $$;

View file

@ -0,0 +1,5 @@
-- Migration to change 'fot' column from INTEGER to VARCHAR(50) to support alphanumeric codes
-- Example: '20000MG'
-- 1. Alter table 'cadastro_fot'
ALTER TABLE cadastro_fot ALTER COLUMN fot TYPE VARCHAR(50);

View file

@ -157,7 +157,7 @@ CREATE TABLE IF NOT EXISTS precos_tipos_eventos (
-- Cadastro FOT -- Cadastro FOT
CREATE TABLE IF NOT EXISTS cadastro_fot ( CREATE TABLE IF NOT EXISTS cadastro_fot (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
fot INTEGER NOT NULL UNIQUE, fot VARCHAR(50) NOT NULL UNIQUE,
empresa_id UUID NOT NULL REFERENCES empresas(id), empresa_id UUID NOT NULL REFERENCES empresas(id),
curso_id UUID NOT NULL REFERENCES cursos(id), curso_id UUID NOT NULL REFERENCES cursos(id),
ano_formatura_id UUID NOT NULL REFERENCES anos_formaturas(id), ano_formatura_id UUID NOT NULL REFERENCES anos_formaturas(id),

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"photum-backend/internal/db/generated" "photum-backend/internal/db/generated"
"strconv"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -197,13 +196,12 @@ func (h *Handler) List(c *gin.Context) {
func (h *Handler) AutoFill(c *gin.Context) { func (h *Handler) AutoFill(c *gin.Context) {
fotNumStr := c.Query("fot") fotNumStr := c.Query("fot")
fotNum, err := strconv.Atoi(fotNumStr) if fotNumStr == "" {
if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "FOT Number required"})
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid FOT Number"})
return return
} }
fotData, err := h.service.AutoFillSearch(c.Request.Context(), int32(fotNum)) fotData, err := h.service.AutoFillSearch(c.Request.Context(), fotNumStr)
if err != nil { if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "FOT not found"}) c.JSON(http.StatusNotFound, gin.H{"error": "FOT not found"})
return return

View file

@ -67,7 +67,7 @@ func (s *Service) ListAll(ctx context.Context) ([]generated.ListTransactionsRow,
return s.queries.ListTransactions(ctx) return s.queries.ListTransactions(ctx)
} }
func (s *Service) AutoFillSearch(ctx context.Context, fotNumber int32) (generated.GetCadastroFotByFotJoinRow, error) { func (s *Service) AutoFillSearch(ctx context.Context, fotNumber string) (generated.GetCadastroFotByFotJoinRow, error) {
return s.queries.GetCadastroFotByFotJoin(ctx, fotNumber) return s.queries.GetCadastroFotByFotJoin(ctx, fotNumber)
} }

View file

@ -596,20 +596,10 @@ export const EventTable: React.FC<EventTableProps> = ({
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<button <button
onClick={(e) => { onClick={(e) => {
const status = calculateTeamStatus(event);
if (!status.profissionaisOK) {
alert("A equipe deve estar completa para aprovar o evento.");
return;
}
onApprove?.(e, event.id); onApprove?.(e, event.id);
}} }}
disabled={!calculateTeamStatus(event).profissionaisOK} className="px-2 py-1 rounded text-xs font-semibold flex items-center gap-1 whitespace-nowrap transition-colors bg-green-500 text-white hover:bg-green-600"
className={`px-2 py-1 rounded text-xs font-semibold flex items-center gap-1 whitespace-nowrap transition-colors ${ title="Aprovar evento"
calculateTeamStatus(event).profissionaisOK
? "bg-green-500 text-white hover:bg-green-600"
: "bg-gray-300 text-gray-500 cursor-not-allowed"
}`}
title={!calculateTeamStatus(event).profissionaisOK ? "Equipe incompleta" : "Aprovar evento"}
> >
<CheckCircle size={12} /> <CheckCircle size={12} />
Aprovar Aprovar

View file

@ -7,7 +7,7 @@ interface FotFormProps {
onCancel: () => void; onCancel: () => void;
onSubmit: (success: boolean) => void; onSubmit: (success: boolean) => void;
token: string; token: string;
existingFots: number[]; // List of existing FOT numbers for validation existingFots: (string | number)[]; // List of existing FOT numbers for validation
initialData?: any; // For editing initialData?: any; // For editing
} }
@ -28,7 +28,7 @@ export const FotForm: React.FC<FotFormProps> = ({ onCancel, onSubmit, token, exi
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
setFormData({ setFormData({
fot: initialData.fot.toString(), fot: initialData.fot ? initialData.fot.toString() : "",
empresa_id: initialData.empresa_id, empresa_id: initialData.empresa_id,
curso_id: initialData.curso_id, curso_id: initialData.curso_id,
ano_formatura_id: initialData.ano_formatura_id, ano_formatura_id: initialData.ano_formatura_id,
@ -73,24 +73,23 @@ export const FotForm: React.FC<FotFormProps> = ({ onCancel, onSubmit, token, exi
}; };
loadData(); loadData();
}, []); }, []);
// Validate FOT uniqueness // Validate FOT uniqueness
const handleFotChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleFotChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value; const val = e.target.value;
setFormData({ ...formData, fot: val }); setFormData({ ...formData, fot: val });
if (!initialData && val && existingFots.includes(parseInt(val))) { // Check uniqueness only if new or changed (and not matching self? Logic simplied: only if not editing self, but passed list does not track who is who, assumed list of ALL. If editing, I AM in the list. OK, let's refine: existingFots should ideally exclude self if editing. We'll leave basic check but maybe warn only. Or just relax check for edit if value matches initial.) if (!initialData && val) {
if (initialData && parseInt(val) === initialData.fot) { if (existingFots.some(f => f.toString() === val)) {
setFotError(null); setFotError(`O FOT ${val} já existe!`);
} else { } else {
if (existingFots.includes(parseInt(val))) { setFotError(null);
setFotError(`O FOT ${val} já existe!`); }
} else { } else if (val && initialData && val !== initialData.fot.toString()) {
setFotError(null); if (existingFots.some(f => f.toString() === val)) {
} setFotError(`O FOT ${val} já existe!`);
} else {
setFotError(null);
} }
} else if (val && existingFots.includes(parseInt(val))) {
setFotError(`O FOT ${val} já existe!`);
} else { } else {
setFotError(null); setFotError(null);
} }
@ -105,7 +104,7 @@ export const FotForm: React.FC<FotFormProps> = ({ onCancel, onSubmit, token, exi
try { try {
const payload = { const payload = {
fot: parseInt(formData.fot), fot: formData.fot, // No parseInt
empresa_id: formData.empresa_id, empresa_id: formData.empresa_id,
curso_id: formData.curso_id, curso_id: formData.curso_id,
ano_formatura_id: formData.ano_formatura_id, // Assuming string UUID from dropdown ano_formatura_id: formData.ano_formatura_id, // Assuming string UUID from dropdown
@ -161,7 +160,7 @@ export const FotForm: React.FC<FotFormProps> = ({ onCancel, onSubmit, token, exi
Número FOT <span className="text-red-500">*</span> Número FOT <span className="text-red-500">*</span>
</label> </label>
<input <input
type="number" type="text"
required required
value={formData.fot} value={formData.fot}
onChange={handleFotChange} onChange={handleFotChange}
@ -170,7 +169,7 @@ export const FotForm: React.FC<FotFormProps> = ({ onCancel, onSubmit, token, exi
? "border-red-300 focus:ring-red-200 focus:border-red-400 bg-red-50" ? "border-red-300 focus:ring-red-200 focus:border-red-400 bg-red-50"
: "border-gray-300 focus:ring-brand-gold focus:border-brand-gold" : "border-gray-300 focus:ring-brand-gold focus:border-brand-gold"
}`} }`}
placeholder="Ex: 25193" placeholder="Ex: 20000MG"
/> />
{fotError && ( {fotError && (
<div className="mt-2 text-red-600 text-sm flex items-center gap-1"> <div className="mt-2 text-red-600 text-sm flex items-center gap-1">

View file

@ -1,6 +1,14 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { UserRole } from "../types"; import { UserRole } from "../types";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
import {
BrowserRouter,
Routes,
Route,
Navigate,
useNavigate,
useLocation,
} from "react-router-dom";
import { import {
Menu, Menu,
X, X,
@ -20,6 +28,7 @@ interface NavbarProps {
} }
export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => { export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
const navigate = useNavigate();
const { user, logout } = useAuth(); const { user, logout } = useAuth();
const [isScrolled, setIsScrolled] = useState(false); const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@ -138,10 +147,11 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
<nav className="fixed w-full z-50 bg-white shadow-sm py-2 sm:py-3"> <nav className="fixed w-full z-50 bg-white shadow-sm py-2 sm:py-3">
<div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-6"> <div className="max-w-7xl mx-auto px-3 sm:px-4 lg:px-6">
<div className="flex justify-between items-center h-12 sm:h-14 md:h-16"> <div className="flex justify-between items-center h-12 sm:h-14 md:h-16">
{/* Logo */} {/* Logo */}
<div <div
className="flex-shrink-0 flex items-center cursor-pointer" className="flex-shrink-0 flex items-center cursor-pointer"
onClick={() => onNavigate("painel")} onClick={() => navigate("/painel")}
> >
<img <img
src="/logo.png" src="/logo.png"

View file

@ -121,17 +121,19 @@ export const ProfessionalForm: React.FC<ProfessionalFormProps> = ({
setIsLoadingCep(true); setIsLoadingCep(true);
try { try {
const response = await fetch( const response = await fetch(
`https://cep.awesomeapi.com.br/json/${cep}` `https://viacep.com.br/ws/${cep}/json/`
); );
if (!response.ok) throw new Error("CEP não encontrado"); if (!response.ok) throw new Error("CEP não encontrado");
const data = await response.json(); const data = await response.json();
if (data.erro) throw new Error("CEP não encontrado");
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
rua: data.address || prev.rua, rua: data.logradouro || prev.rua,
bairro: data.district || prev.bairro, bairro: data.bairro || prev.bairro,
cidade: data.city || prev.cidade, cidade: data.localidade || prev.cidade,
uf: data.state || prev.uf, uf: data.uf || prev.uf,
})); }));
} catch (error) { } catch (error) {
console.error("Erro ao buscar CEP:", error); console.error("Erro ao buscar CEP:", error);

View file

@ -232,28 +232,14 @@ export const Dashboard: React.FC<DashboardProps> = ({
// Filtro por função/role // Filtro por função/role
if (teamRoleFilter !== "all") { if (teamRoleFilter !== "all") {
const professionalFunctions = professional.functions || []; const professionalFunctions = professional.functions || [];
const professionalRole = (professional.role || "").toLowerCase();
// Mapear os valores do filtro para os nomes reais das funções // Check if professional has the selected function ID in their list
const roleMap: { [key: string]: string[] } = { const hasMatchingFunction = professionalFunctions.some(f => f.id === teamRoleFilter);
"fot": ["fotógrafo", "fotógrafos", "photographer"],
"video": ["cinegrafista", "cinegrafistas", "videographer"],
"editor": ["recepcionista", "recepcionistas", "receptionist"],
"assist": ["apoio", "assistente", "assistant"]
};
const targetRoles = roleMap[teamRoleFilter] || []; // Also check if professional.funcao_profissional_id matches (primary role)
const hasMatchingPrimaryRole = professional.funcao_profissional_id === teamRoleFilter;
// Verificar se o profissional tem a função selecionada if (!hasMatchingFunction && !hasMatchingPrimaryRole) return false;
const hasMatchingFunction = professionalFunctions.some(f =>
targetRoles.some(role => (f.nome || "").toLowerCase().includes(role))
);
const hasMatchingRole = targetRoles.some(role =>
professionalRole.includes(role)
);
if (!hasMatchingFunction && !hasMatchingRole) return false;
} }
// Verificar status do assignment para este evento // Verificar status do assignment para este evento
@ -1109,14 +1095,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
{selectedEvent.time} {selectedEvent.time}
</td> </td>
</tr> </tr>
<tr className="hover:bg-gray-50">
<td className="px-4 py-3 text-xs font-semibold text-gray-600 uppercase tracking-wider bg-gray-50">
Qtd Formandos
</td>
<td className="px-4 py-3 text-sm text-gray-900">
{selectedEvent.attendees || "-"}
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -1417,10 +1396,9 @@ export const Dashboard: React.FC<DashboardProps> = ({
className="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-purple focus:border-transparent text-sm" className="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-purple focus:border-transparent text-sm"
> >
<option value="all">Todas as funções</option> <option value="all">Todas as funções</option>
<option value="fot">Fotógrafos</option> {functions?.map(fn => (
<option value="video">Cinegrafistas</option> <option key={fn.id} value={fn.id}>{fn.nome}</option>
<option value="editor">Recepcionistas</option> ))}
<option value="assist">Apoio</option>
</select> </select>
{/* Filtro por status */} {/* Filtro por status */}

View file

@ -1,4 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button } from "../components/Button"; import { Button } from "../components/Button";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { verifyAccessCode } from "../services/apiService"; import { verifyAccessCode } from "../services/apiService";
@ -8,6 +9,7 @@ interface HomeProps {
} }
export const Home: React.FC<HomeProps> = ({ onEnter }) => { export const Home: React.FC<HomeProps> = ({ onEnter }) => {
const navigate = useNavigate();
const [showAccessCodeModal, setShowAccessCodeModal] = useState(false); const [showAccessCodeModal, setShowAccessCodeModal] = useState(false);
const [accessCode, setAccessCode] = useState(""); const [accessCode, setAccessCode] = useState("");
const [showProfessionalPrompt, setShowProfessionalPrompt] = useState(false); const [showProfessionalPrompt, setShowProfessionalPrompt] = useState(false);
@ -30,10 +32,18 @@ export const Home: React.FC<HomeProps> = ({ onEnter }) => {
const res = await verifyAccessCode(accessCode.toUpperCase()); const res = await verifyAccessCode(accessCode.toUpperCase());
if (res.data && res.data.valid) { if (res.data && res.data.valid) {
setShowAccessCodeModal(false); setShowAccessCodeModal(false);
if (isProfessionalRegistration) { // Save token for client
window.location.href = "/cadastro-profissional"; sessionStorage.setItem('accessCodeValidated', 'true');
sessionStorage.setItem('accessCodeData', JSON.stringify({
code: accessCode.toUpperCase(),
empresa_id: res.data.empresa_id,
empresa_nome: res.data.empresa_nome
}));
if (res.data.empresa_id) {
navigate(`/cadastro?empresa_id=${res.data.empresa_id}&empresa_nome=${encodeURIComponent(res.data.empresa_nome || '')}`);
} else { } else {
window.location.href = "/cadastro"; navigate("/cadastro");
} }
} else { } else {
setCodeError(res.data?.error || "Código de acesso inválido ou expirado"); setCodeError(res.data?.error || "Código de acesso inválido ou expirado");
@ -45,8 +55,12 @@ export const Home: React.FC<HomeProps> = ({ onEnter }) => {
const handleProfessionalChoice = (isProfessional: boolean) => { const handleProfessionalChoice = (isProfessional: boolean) => {
setShowProfessionalPrompt(false); setShowProfessionalPrompt(false);
setIsProfessionalRegistration(isProfessional); if (isProfessional) {
setShowAccessCodeModal(true); navigate("/cadastro-profissional");
} else {
setIsProfessionalRegistration(false);
setShowAccessCodeModal(true);
}
}; };
return ( return (

View file

@ -1,4 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
import { Button } from "../components/Button"; import { Button } from "../components/Button";
import { UserRole } from "../types"; import { UserRole } from "../types";
@ -10,6 +11,7 @@ interface LoginProps {
} }
export const Login: React.FC<LoginProps> = ({ onNavigate }) => { export const Login: React.FC<LoginProps> = ({ onNavigate }) => {
const navigate = useNavigate();
const { login, availableUsers } = useAuth(); const { login, availableUsers } = useAuth();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
@ -52,13 +54,13 @@ export const Login: React.FC<LoginProps> = ({ onNavigate }) => {
// Redirecionar baseado no tipo de cadastro selecionado // Redirecionar baseado no tipo de cadastro selecionado
if (selectedCadastroType === 'professional') { if (selectedCadastroType === 'professional') {
window.location.href = "/cadastro-profissional"; navigate("/cadastro-profissional");
} else { } else {
// Para cliente, se o código tem empresa associada, passa na URL // Para cliente, se o código tem empresa associada, passa na URL
if (res.data.empresa_id) { if (res.data.empresa_id) {
window.location.href = `/cadastro?empresa_id=${res.data.empresa_id}&empresa_nome=${encodeURIComponent(res.data.empresa_nome || '')}`; window.location.href = `/cadastro?empresa_id=${res.data.empresa_id}&empresa_nome=${encodeURIComponent(res.data.empresa_nome || '')}`;
} else { } else {
window.location.href = "/cadastro"; navigate("/cadastro");
} }
} }
} else { } else {
@ -72,7 +74,7 @@ export const Login: React.FC<LoginProps> = ({ onNavigate }) => {
const handleProfessionalChoice = (isProfessional: boolean) => { const handleProfessionalChoice = (isProfessional: boolean) => {
setShowProfessionalPrompt(false); setShowProfessionalPrompt(false);
if (isProfessional) { if (isProfessional) {
window.location.href = "/cadastro-profissional"; navigate("/cadastro-profissional");
} else { } else {
setSelectedCadastroType('client'); setSelectedCadastroType('client');
setShowAccessCodeModal(true); setShowAccessCodeModal(true);

View file

@ -4,8 +4,6 @@ import {
ProfessionalData, ProfessionalData,
} from "../components/ProfessionalForm"; } from "../components/ProfessionalForm";
import { useAuth } from "../contexts/AuthContext"; import { useAuth } from "../contexts/AuthContext";
import { X } from "lucide-react";
import { verifyAccessCode } from "../services/apiService";
interface ProfessionalRegisterProps { interface ProfessionalRegisterProps {
onNavigate: (page: string) => void; onNavigate: (page: string) => void;
@ -16,40 +14,8 @@ export const ProfessionalRegister: React.FC<ProfessionalRegisterProps> = ({
}) => { }) => {
const { register } = useAuth(); const { register } = useAuth();
const [isSuccess, setIsSuccess] = useState(false); const [isSuccess, setIsSuccess] = useState(false);
const [showAccessCodeModal, setShowAccessCodeModal] = useState(true);
const [accessCode, setAccessCode] = useState("");
const [codeError, setCodeError] = useState("");
const [isAccessValidated, setIsAccessValidated] = useState(false);
// Verificar se já tem validação ativa na sessão // Removed access code logic as per requirement
useEffect(() => {
const validated = sessionStorage.getItem('professionalAccessValidated');
if (validated === 'true') {
setIsAccessValidated(true);
setShowAccessCodeModal(false);
}
}, []);
const handleVerifyCode = async () => {
if (accessCode.trim() === "") {
setCodeError("Por favor, digite o código de acesso");
return;
}
try {
const res = await verifyAccessCode(accessCode.toUpperCase());
if (res.data && res.data.valid) {
setIsAccessValidated(true);
setShowAccessCodeModal(false);
sessionStorage.setItem('professionalAccessValidated', 'true');
setCodeError("");
} else {
setCodeError(res.data?.error || "Código de acesso inválido ou expirado");
}
} catch (e) {
setCodeError("Erro ao verificar código");
}
};
const handleSubmit = async (professionalData: ProfessionalData) => { const handleSubmit = async (professionalData: ProfessionalData) => {
try { try {
@ -194,107 +160,10 @@ export const ProfessionalRegister: React.FC<ProfessionalRegisterProps> = ({
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-white p-4 py-8 pt-24"> <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-white p-4 py-8 pt-24">
{/* Modal de Código de Acesso Obrigatório */}
{showAccessCodeModal && (
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4"
style={{ backgroundColor: "rgba(0, 0, 0, 0.5)" }}
>
<div
className="bg-white rounded-2xl shadow-2xl max-w-md w-full p-6 sm:p-8"
onClick={(e) => e.stopPropagation()}
style={{
animation: "fadeInScale 0.3s ease-out forwards"
}}
>
<style>{`
@keyframes fadeInScale {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
`}</style>
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-900">
Código de Acesso
</h2>
<button
onClick={() => {
setShowAccessCodeModal(false);
window.location.href = "/";
}}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<X size={24} />
</button>
</div>
<p className="text-gray-600 mb-6">
Digite o código de acesso fornecido pela empresa para continuar com o cadastro.
</p>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
Código de Acesso *
</label>
<input
type="text"
value={accessCode}
onChange={(e) => {
setAccessCode(e.target.value.toUpperCase());
setCodeError("");
}}
onKeyPress={(e) => e.key === "Enter" && handleVerifyCode()}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#B9CF33] focus:border-transparent uppercase"
placeholder="DIGITE O CÓDIGO"
autoFocus
/>
{codeError && (
<p className="text-red-500 text-sm mt-2">{codeError}</p>
)}
</div>
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<p className="text-sm text-yellow-800">
<strong>Atenção:</strong> O código de acesso é fornecido pela
empresa e tem validade temporária.
</p>
</div>
<div className="flex gap-3">
<button
onClick={() => {
setShowAccessCodeModal(false);
window.location.href = "/";
}}
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 font-semibold hover:bg-gray-50 transition-colors"
>
Cancelar
</button>
<button
onClick={handleVerifyCode}
className="flex-1 px-4 py-2 rounded-lg text-white font-semibold transition-colors"
style={{ backgroundColor: "#6B21A8" }}
>
Verificar
</button>
</div>
</div>
</div>
)}
{isAccessValidated && (
<ProfessionalForm <ProfessionalForm
onSubmit={handleSubmit} onSubmit={handleSubmit}
onCancel={() => onNavigate("cadastro")} onCancel={() => onNavigate("cadastro")}
/> />
)}
</div> </div>
); );
}; };

View file

@ -189,16 +189,18 @@ export const TeamPage: React.FC = () => {
setIsLoadingCep(true); setIsLoadingCep(true);
try { try {
const response = await fetch( const response = await fetch(
`https://cep.awesomeapi.com.br/json/${cep}` `https://viacep.com.br/ws/${cep}/json/`
); );
if (!response.ok) throw new Error("CEP não encontrado"); if (!response.ok) throw new Error("CEP não encontrado");
const data = await response.json(); const data = await response.json();
if (data.erro) throw new Error("CEP não encontrado");
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
endereco: `${data.address || ""} ${data.district ? `- ${data.district}` : ""}`.trim() || prev.endereco, endereco: `${data.logradouro || ""} ${data.bairro ? `- ${data.bairro}` : ""}`.trim() || prev.endereco,
cidade: data.city || prev.cidade, cidade: data.localidade || prev.cidade,
uf: data.state || prev.uf, uf: data.uf || prev.uf,
})); }));
} catch (error) { } catch (error) {
console.error("Erro ao buscar CEP:", error); console.error("Erro ao buscar CEP:", error);