Merge branch 'dev' into hml
This commit is contained in:
commit
8469f7d55c
21 changed files with 310 additions and 234 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
|
|
|
||||||
207
backend/internal/db/migrations/006_sync_production.sql
Normal file
207
backend/internal/db/migrations/006_sync_production.sql
Normal 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 $$;
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,27 +73,26 @@ 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);
|
|
||||||
} else {
|
|
||||||
if (existingFots.includes(parseInt(val))) {
|
|
||||||
setFotError(`O FOT ${val} já existe!`);
|
setFotError(`O FOT ${val} já existe!`);
|
||||||
} else {
|
} else {
|
||||||
setFotError(null);
|
setFotError(null);
|
||||||
}
|
}
|
||||||
}
|
} else if (val && initialData && val !== initialData.fot.toString()) {
|
||||||
} else if (val && existingFots.includes(parseInt(val))) {
|
if (existingFots.some(f => f.toString() === val)) {
|
||||||
setFotError(`O FOT ${val} já existe!`);
|
setFotError(`O FOT ${val} já existe!`);
|
||||||
} else {
|
} else {
|
||||||
setFotError(null);
|
setFotError(null);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setFotError(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 */}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
navigate("/cadastro-profissional");
|
||||||
|
} else {
|
||||||
|
setIsProfessionalRegistration(false);
|
||||||
setShowAccessCodeModal(true);
|
setShowAccessCodeModal(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue