Merge pull request #26 from rede5/Front-back-integracao-task7
Correção da Listagem de Agenda, Filtro de Usuário e Mapeamento de Dados Resolve problemas críticos no módulo de Agenda, garantindo que os eventos sejam exibidos corretamente para o usuário logado e que as informações na interface (como o número da turma e nome do evento) correspondam aos dados reais do banco, eliminando a exibição de IDs internos e campos vazios. Alterações Realizadas 🔙 Backend Banco de Dados: Adicionada coluna user_id na tabela agenda para vincular o evento ao seu criador. Queries: CreateAgenda atualizado para salvar o user_id. Nova query ListAgendasByUser criada para filtrar os eventos pelo ID do usuário. Lógica: Atualização nos Services e Handlers para identificar o usuário via token JWT e aplicar o filtro corretamente. 🖥️ Frontend Correção de Reatividade ( DataContext ): Integração do useAuth para garantir que a lista de eventos (função fetchEvents ) seja recarregada automaticamente assim que o login é realizado. Mapeamento de Dados: FOT: Ajustado para exibir o fot_numero (ex: 25189) em vez do UUID. Nome do Evento: Criado fallback para usar o "Tipo de Evento" (ex: "Churrasco") como título caso o nome não seja informado. Formandos: Corrigido o mapeamento de qtd_formandos para attendees. Interface ( EventTable & Dashboard ): Tabelas e telas de detalhes atualizadas para usar os campos mapeados corretamente. Correção de erro de TypeScript no enum EventStatus (troca de COMPLETED para DELIVERED). Como Testar Filtro: Logar com um usuário cliente e confirmar que ele vê apenas os seus eventos. Listagem: Verificar se a coluna "FOT" mostra um número simples e se colunas como "Curso" e "Instituição" estão preenchidas. Detalhes: Abrir um evento e confirmar se "Qtd Formandos" aparece (ex: 35) e se o título do evento está correto mesmo sem observações preenchidas
This commit is contained in:
commit
52e167475c
24 changed files with 1005 additions and 370 deletions
|
|
@ -828,6 +828,14 @@ const docTemplate = `{
|
||||||
"cadastro_fot"
|
"cadastro_fot"
|
||||||
],
|
],
|
||||||
"summary": "List all FOT records",
|
"summary": "List all FOT records",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by Company ID",
|
||||||
|
"name": "empresa_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
|
|
@ -2584,6 +2592,9 @@ const docTemplate = `{
|
||||||
"ativo": {
|
"ativo": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"company_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"company_name": {
|
"company_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -822,6 +822,14 @@
|
||||||
"cadastro_fot"
|
"cadastro_fot"
|
||||||
],
|
],
|
||||||
"summary": "List all FOT records",
|
"summary": "List all FOT records",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by Company ID",
|
||||||
|
"name": "empresa_id",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
|
|
@ -2578,6 +2586,9 @@
|
||||||
"ativo": {
|
"ativo": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"company_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"company_name": {
|
"company_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,8 @@ definitions:
|
||||||
properties:
|
properties:
|
||||||
ativo:
|
ativo:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
company_id:
|
||||||
|
type: string
|
||||||
company_name:
|
company_name:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
|
|
@ -963,6 +965,11 @@ paths:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Filter by Company ID
|
||||||
|
in: query
|
||||||
|
name: empresa_id
|
||||||
|
type: string
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,14 @@ func (h *Handler) Create(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
agenda, err := h.service.Create(c.Request.Context(), req)
|
userIDStr := c.GetString("userID")
|
||||||
|
userID, err := uuid.Parse(userIDStr)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Usuário não autenticado"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
agenda, err := h.service.Create(c.Request.Context(), userID, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao criar agenda: " + err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao criar agenda: " + err.Error()})
|
||||||
return
|
return
|
||||||
|
|
@ -52,7 +59,11 @@ func (h *Handler) Create(c *gin.Context) {
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 500 {object} map[string]string
|
||||||
// @Router /api/agenda [get]
|
// @Router /api/agenda [get]
|
||||||
func (h *Handler) List(c *gin.Context) {
|
func (h *Handler) List(c *gin.Context) {
|
||||||
agendas, err := h.service.List(c.Request.Context())
|
userIDStr := c.GetString("userID")
|
||||||
|
role := c.GetString("role")
|
||||||
|
userID, _ := uuid.Parse(userIDStr)
|
||||||
|
|
||||||
|
agendas, err := h.service.List(c.Request.Context(), userID, role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao listar agendas: " + err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao listar agendas: " + err.Error()})
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func (s *Service) CalculateStatus(fotoFaltante, recepFaltante, cineFaltante int3
|
||||||
return "ERRO"
|
return "ERRO"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Create(ctx context.Context, req CreateAgendaRequest) (generated.Agenda, error) {
|
func (s *Service) Create(ctx context.Context, userID uuid.UUID, req CreateAgendaRequest) (generated.Agenda, error) {
|
||||||
status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante)
|
status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante)
|
||||||
|
|
||||||
params := generated.CreateAgendaParams{
|
params := generated.CreateAgendaParams{
|
||||||
|
|
@ -86,12 +86,64 @@ func (s *Service) Create(ctx context.Context, req CreateAgendaRequest) (generate
|
||||||
CineFaltante: pgtype.Int4{Int32: req.CineFaltante, Valid: true},
|
CineFaltante: pgtype.Int4{Int32: req.CineFaltante, Valid: true},
|
||||||
LogisticaObservacoes: pgtype.Text{String: req.LogisticaObservacoes, Valid: req.LogisticaObservacoes != ""},
|
LogisticaObservacoes: pgtype.Text{String: req.LogisticaObservacoes, Valid: req.LogisticaObservacoes != ""},
|
||||||
PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true},
|
PreVenda: pgtype.Bool{Bool: req.PreVenda, Valid: true},
|
||||||
|
UserID: pgtype.UUID{Bytes: userID, Valid: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.queries.CreateAgenda(ctx, params)
|
return s.queries.CreateAgenda(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) List(ctx context.Context) ([]generated.ListAgendasRow, error) {
|
func (s *Service) List(ctx context.Context, userID uuid.UUID, role string) ([]generated.ListAgendasRow, error) {
|
||||||
|
// If role is CLIENT (cliente), filter by userID
|
||||||
|
if role == "cliente" || role == "EVENT_OWNER" {
|
||||||
|
rows, err := s.queries.ListAgendasByUser(ctx, pgtype.UUID{Bytes: userID, Valid: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Convert ListAgendasByUserRow to ListAgendasRow (they are identical structurally but different types in Go)
|
||||||
|
// To avoid manual conversion if types differ slightly by name but not structure, we might need a common interface or cast.
|
||||||
|
// Since sqlc generates separate structs, we have to map them.
|
||||||
|
var result []generated.ListAgendasRow
|
||||||
|
for _, r := range rows {
|
||||||
|
result = append(result, generated.ListAgendasRow{
|
||||||
|
ID: r.ID,
|
||||||
|
UserID: r.UserID,
|
||||||
|
FotID: r.FotID,
|
||||||
|
DataEvento: r.DataEvento,
|
||||||
|
TipoEventoID: r.TipoEventoID,
|
||||||
|
ObservacoesEvento: r.ObservacoesEvento,
|
||||||
|
LocalEvento: r.LocalEvento,
|
||||||
|
Endereco: r.Endereco,
|
||||||
|
Horario: r.Horario,
|
||||||
|
QtdFormandos: r.QtdFormandos,
|
||||||
|
QtdFotografos: r.QtdFotografos,
|
||||||
|
QtdRecepcionistas: r.QtdRecepcionistas,
|
||||||
|
QtdCinegrafistas: r.QtdCinegrafistas,
|
||||||
|
QtdEstudios: r.QtdEstudios,
|
||||||
|
QtdPontoFoto: r.QtdPontoFoto,
|
||||||
|
QtdPontoID: r.QtdPontoID,
|
||||||
|
QtdPontoDecorado: r.QtdPontoDecorado,
|
||||||
|
QtdPontosLed: r.QtdPontosLed,
|
||||||
|
QtdPlataforma360: r.QtdPlataforma360,
|
||||||
|
StatusProfissionais: r.StatusProfissionais,
|
||||||
|
FotoFaltante: r.FotoFaltante,
|
||||||
|
RecepFaltante: r.RecepFaltante,
|
||||||
|
CineFaltante: r.CineFaltante,
|
||||||
|
LogisticaObservacoes: r.LogisticaObservacoes,
|
||||||
|
PreVenda: r.PreVenda,
|
||||||
|
CriadoEm: r.CriadoEm,
|
||||||
|
AtualizadoEm: r.AtualizadoEm,
|
||||||
|
FotNumero: r.FotNumero,
|
||||||
|
Instituicao: r.Instituicao,
|
||||||
|
CursoNome: r.CursoNome,
|
||||||
|
EmpresaNome: r.EmpresaNome,
|
||||||
|
AnoSemestre: r.AnoSemestre,
|
||||||
|
ObservacoesFot: r.ObservacoesFot,
|
||||||
|
TipoEventoNome: r.TipoEventoNome,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
return s.queries.ListAgendas(ctx)
|
return s.queries.ListAgendas(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ type userResponse struct {
|
||||||
Ativo bool `json:"ativo"`
|
Ativo bool `json:"ativo"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Phone string `json:"phone,omitempty"`
|
Phone string `json:"phone,omitempty"`
|
||||||
|
CompanyID string `json:"company_id,omitempty"`
|
||||||
CompanyName string `json:"company_name,omitempty"`
|
CompanyName string `json:"company_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,14 +187,27 @@ func (h *Handler) Login(c *gin.Context) {
|
||||||
MaxAge: 15 * 60, // 15 mins
|
MaxAge: 15 * 60, // 15 mins
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Handle Nullable Fields
|
||||||
|
var companyID, companyName string
|
||||||
|
if user.EmpresaID.Valid {
|
||||||
|
companyID = uuid.UUID(user.EmpresaID.Bytes).String()
|
||||||
|
}
|
||||||
|
if user.EmpresaNome.Valid {
|
||||||
|
companyName = user.EmpresaNome.String
|
||||||
|
}
|
||||||
|
|
||||||
resp := loginResponse{
|
resp := loginResponse{
|
||||||
AccessToken: tokenPair.AccessToken,
|
AccessToken: tokenPair.AccessToken,
|
||||||
ExpiresAt: "2025-...", // logic to calculate if needed, or remove field
|
ExpiresAt: "2025-...", // logic to calculate if needed, or remove field
|
||||||
User: userResponse{
|
User: userResponse{
|
||||||
ID: uuid.UUID(user.ID.Bytes).String(),
|
ID: uuid.UUID(user.ID.Bytes).String(),
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Role: user.Role,
|
Role: user.Role,
|
||||||
Ativo: user.Ativo,
|
Ativo: user.Ativo,
|
||||||
|
Name: user.Nome,
|
||||||
|
Phone: user.Whatsapp,
|
||||||
|
CompanyID: companyID,
|
||||||
|
CompanyName: companyName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -571,16 +585,22 @@ func (h *Handler) GetUser(c *gin.Context) {
|
||||||
if user.EmpresaNome.Valid {
|
if user.EmpresaNome.Valid {
|
||||||
empresaNome = user.EmpresaNome.String
|
empresaNome = user.EmpresaNome.String
|
||||||
}
|
}
|
||||||
|
var empresaID string
|
||||||
|
if user.EmpresaID.Valid {
|
||||||
|
empresaID = uuid.UUID(user.EmpresaID.Bytes).String()
|
||||||
|
}
|
||||||
|
|
||||||
resp := map[string]interface{}{
|
resp := loginResponse{
|
||||||
"id": uuid.UUID(user.ID.Bytes).String(),
|
User: userResponse{
|
||||||
"email": user.Email,
|
ID: uuid.UUID(user.ID.Bytes).String(),
|
||||||
"role": user.Role,
|
Email: user.Email,
|
||||||
"ativo": user.Ativo,
|
Role: user.Role,
|
||||||
"created_at": user.CriadoEm.Time,
|
Ativo: user.Ativo,
|
||||||
"name": user.Nome,
|
Name: user.Nome,
|
||||||
"phone": user.Whatsapp,
|
Phone: user.Whatsapp,
|
||||||
"company_name": empresaNome,
|
CompanyID: empresaID,
|
||||||
|
CompanyName: empresaNome,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, resp)
|
c.JSON(http.StatusOK, resp)
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,8 @@ type TokenPair struct {
|
||||||
RefreshToken string
|
RefreshToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Login(ctx context.Context, email, senha string) (*TokenPair, *generated.Usuario, *generated.GetProfissionalByUsuarioIDRow, error) {
|
func (s *Service) Login(ctx context.Context, email, senha string) (*TokenPair, *generated.GetUsuarioByEmailRow, *generated.GetProfissionalByUsuarioIDRow, error) {
|
||||||
|
// The query now returns a Row with joined fields, not just Usuario struct
|
||||||
user, err := s.queries.GetUsuarioByEmail(ctx, email)
|
user, err := s.queries.GetUsuarioByEmail(ctx, email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, errors.New("invalid credentials")
|
return nil, nil, nil, errors.New("invalid credentials")
|
||||||
|
|
|
||||||
|
|
@ -128,19 +128,50 @@ func (h *Handler) Create(c *gin.Context) {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
|
// @Param empresa_id query string false "Filter by Company ID"
|
||||||
// @Success 200 {array} CadastroFotResponse
|
// @Success 200 {array} CadastroFotResponse
|
||||||
// @Router /api/cadastro-fot [get]
|
// @Router /api/cadastro-fot [get]
|
||||||
func (h *Handler) List(c *gin.Context) {
|
func (h *Handler) List(c *gin.Context) {
|
||||||
rows, err := h.service.List(c.Request.Context())
|
empresaID := c.Query("empresa_id")
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
var response []CadastroFotResponse
|
||||||
return
|
|
||||||
|
if empresaID != "" {
|
||||||
|
rows, err := h.service.ListByEmpresa(c.Request.Context(), empresaID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Conversion for specific row type
|
||||||
|
for _, r := range rows {
|
||||||
|
response = append(response, CadastroFotResponse{
|
||||||
|
ID: uuid.UUID(r.ID.Bytes).String(),
|
||||||
|
Fot: r.Fot,
|
||||||
|
EmpresaID: uuid.UUID(r.EmpresaID.Bytes).String(),
|
||||||
|
EmpresaNome: r.EmpresaNome,
|
||||||
|
CursoID: uuid.UUID(r.CursoID.Bytes).String(),
|
||||||
|
CursoNome: r.CursoNome,
|
||||||
|
AnoFormaturaID: uuid.UUID(r.AnoFormaturaID.Bytes).String(),
|
||||||
|
AnoFormaturaLabel: r.AnoFormaturaLabel,
|
||||||
|
Instituicao: r.Instituicao.String,
|
||||||
|
Cidade: r.Cidade.String,
|
||||||
|
Estado: r.Estado.String,
|
||||||
|
Observacoes: r.Observacoes.String,
|
||||||
|
GastosCaptacao: fromPgNumeric(r.GastosCaptacao),
|
||||||
|
PreVenda: r.PreVenda.Bool,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rows, err := h.service.List(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, r := range rows {
|
||||||
|
response = append(response, toListResponse(r))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response := []CadastroFotResponse{}
|
|
||||||
for _, r := range rows {
|
|
||||||
response = append(response, toListResponse(r))
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,15 @@ func (s *Service) List(ctx context.Context) ([]generated.ListCadastroFotRow, err
|
||||||
return s.queries.ListCadastroFot(ctx)
|
return s.queries.ListCadastroFot(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListByEmpresa(ctx context.Context, empresaID string) ([]generated.ListCadastroFotByEmpresaRow, error) {
|
||||||
|
uuidVal, err := uuid.Parse(empresaID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid empresa_id")
|
||||||
|
}
|
||||||
|
// Note: ListCadastroFotByEmpresaRow is nearly identical to ListCadastroFotRow but we use the generated type
|
||||||
|
return s.queries.ListCadastroFotByEmpresa(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) GetByID(ctx context.Context, id string) (*generated.GetCadastroFotByIDRow, error) {
|
func (s *Service) GetByID(ctx context.Context, id string) (*generated.GetCadastroFotByIDRow, error) {
|
||||||
uuidVal, err := uuid.Parse(id)
|
uuidVal, err := uuid.Parse(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,11 @@ INSERT INTO agenda (
|
||||||
recep_faltante,
|
recep_faltante,
|
||||||
cine_faltante,
|
cine_faltante,
|
||||||
logistica_observacoes,
|
logistica_observacoes,
|
||||||
pre_venda
|
pre_venda,
|
||||||
|
user_id
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24
|
||||||
) RETURNING id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em
|
) RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateAgendaParams struct {
|
type CreateAgendaParams struct {
|
||||||
|
|
@ -65,6 +66,7 @@ type CreateAgendaParams struct {
|
||||||
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||||
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||||
PreVenda pgtype.Bool `json:"pre_venda"`
|
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||||
|
UserID pgtype.UUID `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Agenda, error) {
|
func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Agenda, error) {
|
||||||
|
|
@ -92,10 +94,12 @@ func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Age
|
||||||
arg.CineFaltante,
|
arg.CineFaltante,
|
||||||
arg.LogisticaObservacoes,
|
arg.LogisticaObservacoes,
|
||||||
arg.PreVenda,
|
arg.PreVenda,
|
||||||
|
arg.UserID,
|
||||||
)
|
)
|
||||||
var i Agenda
|
var i Agenda
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.UserID,
|
||||||
&i.FotID,
|
&i.FotID,
|
||||||
&i.DataEvento,
|
&i.DataEvento,
|
||||||
&i.TipoEventoID,
|
&i.TipoEventoID,
|
||||||
|
|
@ -136,7 +140,7 @@ func (q *Queries) DeleteAgenda(ctx context.Context, id pgtype.UUID) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAgenda = `-- name: GetAgenda :one
|
const getAgenda = `-- name: GetAgenda :one
|
||||||
SELECT id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em FROM agenda
|
SELECT id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em FROM agenda
|
||||||
WHERE id = $1 LIMIT 1
|
WHERE id = $1 LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -145,6 +149,7 @@ func (q *Queries) GetAgenda(ctx context.Context, id pgtype.UUID) (Agenda, error)
|
||||||
var i Agenda
|
var i Agenda
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.UserID,
|
||||||
&i.FotID,
|
&i.FotID,
|
||||||
&i.DataEvento,
|
&i.DataEvento,
|
||||||
&i.TipoEventoID,
|
&i.TipoEventoID,
|
||||||
|
|
@ -176,7 +181,7 @@ func (q *Queries) GetAgenda(ctx context.Context, id pgtype.UUID) (Agenda, error)
|
||||||
|
|
||||||
const listAgendas = `-- name: ListAgendas :many
|
const listAgendas = `-- name: ListAgendas :many
|
||||||
SELECT
|
SELECT
|
||||||
a.id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em,
|
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em,
|
||||||
cf.fot as fot_numero,
|
cf.fot as fot_numero,
|
||||||
cf.instituicao,
|
cf.instituicao,
|
||||||
c.nome as curso_nome,
|
c.nome as curso_nome,
|
||||||
|
|
@ -195,6 +200,7 @@ ORDER BY a.data_evento
|
||||||
|
|
||||||
type ListAgendasRow struct {
|
type ListAgendasRow struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
|
UserID pgtype.UUID `json:"user_id"`
|
||||||
FotID pgtype.UUID `json:"fot_id"`
|
FotID pgtype.UUID `json:"fot_id"`
|
||||||
DataEvento pgtype.Date `json:"data_evento"`
|
DataEvento pgtype.Date `json:"data_evento"`
|
||||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||||
|
|
@ -240,6 +246,119 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) {
|
||||||
var i ListAgendasRow
|
var i ListAgendasRow
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.UserID,
|
||||||
|
&i.FotID,
|
||||||
|
&i.DataEvento,
|
||||||
|
&i.TipoEventoID,
|
||||||
|
&i.ObservacoesEvento,
|
||||||
|
&i.LocalEvento,
|
||||||
|
&i.Endereco,
|
||||||
|
&i.Horario,
|
||||||
|
&i.QtdFormandos,
|
||||||
|
&i.QtdFotografos,
|
||||||
|
&i.QtdRecepcionistas,
|
||||||
|
&i.QtdCinegrafistas,
|
||||||
|
&i.QtdEstudios,
|
||||||
|
&i.QtdPontoFoto,
|
||||||
|
&i.QtdPontoID,
|
||||||
|
&i.QtdPontoDecorado,
|
||||||
|
&i.QtdPontosLed,
|
||||||
|
&i.QtdPlataforma360,
|
||||||
|
&i.StatusProfissionais,
|
||||||
|
&i.FotoFaltante,
|
||||||
|
&i.RecepFaltante,
|
||||||
|
&i.CineFaltante,
|
||||||
|
&i.LogisticaObservacoes,
|
||||||
|
&i.PreVenda,
|
||||||
|
&i.CriadoEm,
|
||||||
|
&i.AtualizadoEm,
|
||||||
|
&i.FotNumero,
|
||||||
|
&i.Instituicao,
|
||||||
|
&i.CursoNome,
|
||||||
|
&i.EmpresaNome,
|
||||||
|
&i.AnoSemestre,
|
||||||
|
&i.ObservacoesFot,
|
||||||
|
&i.TipoEventoNome,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const listAgendasByUser = `-- name: ListAgendasByUser :many
|
||||||
|
SELECT
|
||||||
|
a.id, a.user_id, a.fot_id, a.data_evento, a.tipo_evento_id, a.observacoes_evento, a.local_evento, a.endereco, a.horario, a.qtd_formandos, a.qtd_fotografos, a.qtd_recepcionistas, a.qtd_cinegrafistas, a.qtd_estudios, a.qtd_ponto_foto, a.qtd_ponto_id, a.qtd_ponto_decorado, a.qtd_pontos_led, a.qtd_plataforma_360, a.status_profissionais, a.foto_faltante, a.recep_faltante, a.cine_faltante, a.logistica_observacoes, a.pre_venda, a.criado_em, a.atualizado_em,
|
||||||
|
cf.fot as fot_numero,
|
||||||
|
cf.instituicao,
|
||||||
|
c.nome as curso_nome,
|
||||||
|
e.nome as empresa_nome,
|
||||||
|
af.ano_semestre,
|
||||||
|
cf.observacoes as observacoes_fot,
|
||||||
|
te.nome as tipo_evento_nome
|
||||||
|
FROM agenda a
|
||||||
|
JOIN cadastro_fot cf ON a.fot_id = cf.id
|
||||||
|
JOIN cursos c ON cf.curso_id = c.id
|
||||||
|
JOIN empresas e ON cf.empresa_id = e.id
|
||||||
|
JOIN anos_formaturas af ON cf.ano_formatura_id = af.id
|
||||||
|
JOIN tipos_eventos te ON a.tipo_evento_id = te.id
|
||||||
|
WHERE a.user_id = $1
|
||||||
|
ORDER BY a.data_evento
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListAgendasByUserRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
UserID pgtype.UUID `json:"user_id"`
|
||||||
|
FotID pgtype.UUID `json:"fot_id"`
|
||||||
|
DataEvento pgtype.Date `json:"data_evento"`
|
||||||
|
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||||
|
ObservacoesEvento pgtype.Text `json:"observacoes_evento"`
|
||||||
|
LocalEvento pgtype.Text `json:"local_evento"`
|
||||||
|
Endereco pgtype.Text `json:"endereco"`
|
||||||
|
Horario pgtype.Text `json:"horario"`
|
||||||
|
QtdFormandos pgtype.Int4 `json:"qtd_formandos"`
|
||||||
|
QtdFotografos pgtype.Int4 `json:"qtd_fotografos"`
|
||||||
|
QtdRecepcionistas pgtype.Int4 `json:"qtd_recepcionistas"`
|
||||||
|
QtdCinegrafistas pgtype.Int4 `json:"qtd_cinegrafistas"`
|
||||||
|
QtdEstudios pgtype.Int4 `json:"qtd_estudios"`
|
||||||
|
QtdPontoFoto pgtype.Int4 `json:"qtd_ponto_foto"`
|
||||||
|
QtdPontoID pgtype.Int4 `json:"qtd_ponto_id"`
|
||||||
|
QtdPontoDecorado pgtype.Int4 `json:"qtd_ponto_decorado"`
|
||||||
|
QtdPontosLed pgtype.Int4 `json:"qtd_pontos_led"`
|
||||||
|
QtdPlataforma360 pgtype.Int4 `json:"qtd_plataforma_360"`
|
||||||
|
StatusProfissionais pgtype.Text `json:"status_profissionais"`
|
||||||
|
FotoFaltante pgtype.Int4 `json:"foto_faltante"`
|
||||||
|
RecepFaltante pgtype.Int4 `json:"recep_faltante"`
|
||||||
|
CineFaltante pgtype.Int4 `json:"cine_faltante"`
|
||||||
|
LogisticaObservacoes pgtype.Text `json:"logistica_observacoes"`
|
||||||
|
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||||
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
|
FotNumero int32 `json:"fot_numero"`
|
||||||
|
Instituicao pgtype.Text `json:"instituicao"`
|
||||||
|
CursoNome string `json:"curso_nome"`
|
||||||
|
EmpresaNome string `json:"empresa_nome"`
|
||||||
|
AnoSemestre string `json:"ano_semestre"`
|
||||||
|
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
||||||
|
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([]ListAgendasByUserRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, listAgendasByUser, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ListAgendasByUserRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i ListAgendasByUserRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.UserID,
|
||||||
&i.FotID,
|
&i.FotID,
|
||||||
&i.DataEvento,
|
&i.DataEvento,
|
||||||
&i.TipoEventoID,
|
&i.TipoEventoID,
|
||||||
|
|
@ -311,7 +430,7 @@ SET
|
||||||
pre_venda = $24,
|
pre_venda = $24,
|
||||||
atualizado_em = NOW()
|
atualizado_em = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em
|
RETURNING id, user_id, fot_id, data_evento, tipo_evento_id, observacoes_evento, local_evento, endereco, horario, qtd_formandos, qtd_fotografos, qtd_recepcionistas, qtd_cinegrafistas, qtd_estudios, qtd_ponto_foto, qtd_ponto_id, qtd_ponto_decorado, qtd_pontos_led, qtd_plataforma_360, status_profissionais, foto_faltante, recep_faltante, cine_faltante, logistica_observacoes, pre_venda, criado_em, atualizado_em
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateAgendaParams struct {
|
type UpdateAgendaParams struct {
|
||||||
|
|
@ -371,6 +490,7 @@ func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Age
|
||||||
var i Agenda
|
var i Agenda
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.UserID,
|
||||||
&i.FotID,
|
&i.FotID,
|
||||||
&i.DataEvento,
|
&i.DataEvento,
|
||||||
&i.TipoEventoID,
|
&i.TipoEventoID,
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,76 @@ func (q *Queries) ListCadastroFot(ctx context.Context) ([]ListCadastroFotRow, er
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const listCadastroFotByEmpresa = `-- name: ListCadastroFotByEmpresa :many
|
||||||
|
SELECT
|
||||||
|
c.id, c.fot, c.empresa_id, c.curso_id, c.ano_formatura_id, c.instituicao, c.cidade, c.estado, c.observacoes, c.gastos_captacao, c.pre_venda, c.created_at, c.updated_at,
|
||||||
|
e.nome as empresa_nome,
|
||||||
|
cur.nome as curso_nome,
|
||||||
|
a.ano_semestre as ano_formatura_label
|
||||||
|
FROM cadastro_fot c
|
||||||
|
JOIN empresas e ON c.empresa_id = e.id
|
||||||
|
JOIN cursos cur ON c.curso_id = cur.id
|
||||||
|
JOIN anos_formaturas a ON c.ano_formatura_id = a.id
|
||||||
|
WHERE c.empresa_id = $1
|
||||||
|
ORDER BY c.fot DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
type ListCadastroFotByEmpresaRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Fot int32 `json:"fot"`
|
||||||
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
|
CursoID pgtype.UUID `json:"curso_id"`
|
||||||
|
AnoFormaturaID pgtype.UUID `json:"ano_formatura_id"`
|
||||||
|
Instituicao pgtype.Text `json:"instituicao"`
|
||||||
|
Cidade pgtype.Text `json:"cidade"`
|
||||||
|
Estado pgtype.Text `json:"estado"`
|
||||||
|
Observacoes pgtype.Text `json:"observacoes"`
|
||||||
|
GastosCaptacao pgtype.Numeric `json:"gastos_captacao"`
|
||||||
|
PreVenda pgtype.Bool `json:"pre_venda"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
EmpresaNome string `json:"empresa_nome"`
|
||||||
|
CursoNome string `json:"curso_nome"`
|
||||||
|
AnoFormaturaLabel string `json:"ano_formatura_label"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) ListCadastroFotByEmpresa(ctx context.Context, empresaID pgtype.UUID) ([]ListCadastroFotByEmpresaRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, listCadastroFotByEmpresa, empresaID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []ListCadastroFotByEmpresaRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i ListCadastroFotByEmpresaRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Fot,
|
||||||
|
&i.EmpresaID,
|
||||||
|
&i.CursoID,
|
||||||
|
&i.AnoFormaturaID,
|
||||||
|
&i.Instituicao,
|
||||||
|
&i.Cidade,
|
||||||
|
&i.Estado,
|
||||||
|
&i.Observacoes,
|
||||||
|
&i.GastosCaptacao,
|
||||||
|
&i.PreVenda,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.EmpresaNome,
|
||||||
|
&i.CursoNome,
|
||||||
|
&i.AnoFormaturaLabel,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const updateCadastroFot = `-- name: UpdateCadastroFot :one
|
const updateCadastroFot = `-- name: UpdateCadastroFot :one
|
||||||
UPDATE cadastro_fot SET
|
UPDATE cadastro_fot SET
|
||||||
fot = $2,
|
fot = $2,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
type Agenda struct {
|
type Agenda struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
|
UserID pgtype.UUID `json:"user_id"`
|
||||||
FotID pgtype.UUID `json:"fot_id"`
|
FotID pgtype.UUID `json:"fot_id"`
|
||||||
DataEvento pgtype.Date `json:"data_evento"`
|
DataEvento pgtype.Date `json:"data_evento"`
|
||||||
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
TipoEventoID pgtype.UUID `json:"tipo_evento_id"`
|
||||||
|
|
|
||||||
|
|
@ -82,13 +82,35 @@ func (q *Queries) DeleteUsuario(ctx context.Context, id pgtype.UUID) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUsuarioByEmail = `-- name: GetUsuarioByEmail :one
|
const getUsuarioByEmail = `-- name: GetUsuarioByEmail :one
|
||||||
SELECT id, email, senha_hash, role, ativo, criado_em, atualizado_em FROM usuarios
|
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
WHERE email = $1 LIMIT 1
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
|
e.id as empresa_id,
|
||||||
|
e.nome as empresa_nome
|
||||||
|
FROM usuarios u
|
||||||
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
|
LEFT JOIN cadastro_clientes cc ON u.id = cc.usuario_id
|
||||||
|
LEFT JOIN empresas e ON cc.empresa_id = e.id
|
||||||
|
WHERE u.email = $1 LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetUsuarioByEmail(ctx context.Context, email string) (Usuario, error) {
|
type GetUsuarioByEmailRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
SenhaHash string `json:"senha_hash"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Ativo bool `json:"ativo"`
|
||||||
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
|
Nome string `json:"nome"`
|
||||||
|
Whatsapp string `json:"whatsapp"`
|
||||||
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
|
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUsuarioByEmail(ctx context.Context, email string) (GetUsuarioByEmailRow, error) {
|
||||||
row := q.db.QueryRow(ctx, getUsuarioByEmail, email)
|
row := q.db.QueryRow(ctx, getUsuarioByEmail, email)
|
||||||
var i Usuario
|
var i GetUsuarioByEmailRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
|
|
@ -97,6 +119,10 @@ func (q *Queries) GetUsuarioByEmail(ctx context.Context, email string) (Usuario,
|
||||||
&i.Ativo,
|
&i.Ativo,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
&i.Nome,
|
||||||
|
&i.Whatsapp,
|
||||||
|
&i.EmpresaID,
|
||||||
|
&i.EmpresaNome,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -105,6 +131,7 @@ const getUsuarioByID = `-- name: GetUsuarioByID :one
|
||||||
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
|
e.id as empresa_id,
|
||||||
e.nome as empresa_nome
|
e.nome as empresa_nome
|
||||||
FROM usuarios u
|
FROM usuarios u
|
||||||
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
|
|
@ -123,6 +150,7 @@ type GetUsuarioByIDRow struct {
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
Nome string `json:"nome"`
|
Nome string `json:"nome"`
|
||||||
Whatsapp string `json:"whatsapp"`
|
Whatsapp string `json:"whatsapp"`
|
||||||
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,6 +167,7 @@ func (q *Queries) GetUsuarioByID(ctx context.Context, id pgtype.UUID) (GetUsuari
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
&i.Nome,
|
&i.Nome,
|
||||||
&i.Whatsapp,
|
&i.Whatsapp,
|
||||||
|
&i.EmpresaID,
|
||||||
&i.EmpresaNome,
|
&i.EmpresaNome,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
|
|
@ -190,6 +219,7 @@ const listUsuariosPending = `-- name: ListUsuariosPending :many
|
||||||
SELECT u.id, u.email, u.role, u.ativo, u.criado_em,
|
SELECT u.id, u.email, u.role, u.ativo, u.criado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
|
e.id as empresa_id,
|
||||||
e.nome as empresa_nome
|
e.nome as empresa_nome
|
||||||
FROM usuarios u
|
FROM usuarios u
|
||||||
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
|
|
@ -207,6 +237,7 @@ type ListUsuariosPendingRow struct {
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
Nome string `json:"nome"`
|
Nome string `json:"nome"`
|
||||||
Whatsapp string `json:"whatsapp"`
|
Whatsapp string `json:"whatsapp"`
|
||||||
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,6 +258,7 @@ func (q *Queries) ListUsuariosPending(ctx context.Context) ([]ListUsuariosPendin
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.Nome,
|
&i.Nome,
|
||||||
&i.Whatsapp,
|
&i.Whatsapp,
|
||||||
|
&i.EmpresaID,
|
||||||
&i.EmpresaNome,
|
&i.EmpresaNome,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ INSERT INTO agenda (
|
||||||
recep_faltante,
|
recep_faltante,
|
||||||
cine_faltante,
|
cine_faltante,
|
||||||
logistica_observacoes,
|
logistica_observacoes,
|
||||||
pre_venda
|
pre_venda,
|
||||||
|
user_id
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24
|
||||||
) RETURNING *;
|
) RETURNING *;
|
||||||
|
|
||||||
-- name: GetAgenda :one
|
-- name: GetAgenda :one
|
||||||
|
|
@ -49,6 +50,25 @@ JOIN anos_formaturas af ON cf.ano_formatura_id = af.id
|
||||||
JOIN tipos_eventos te ON a.tipo_evento_id = te.id
|
JOIN tipos_eventos te ON a.tipo_evento_id = te.id
|
||||||
ORDER BY a.data_evento;
|
ORDER BY a.data_evento;
|
||||||
|
|
||||||
|
-- name: ListAgendasByUser :many
|
||||||
|
SELECT
|
||||||
|
a.*,
|
||||||
|
cf.fot as fot_numero,
|
||||||
|
cf.instituicao,
|
||||||
|
c.nome as curso_nome,
|
||||||
|
e.nome as empresa_nome,
|
||||||
|
af.ano_semestre,
|
||||||
|
cf.observacoes as observacoes_fot,
|
||||||
|
te.nome as tipo_evento_nome
|
||||||
|
FROM agenda a
|
||||||
|
JOIN cadastro_fot cf ON a.fot_id = cf.id
|
||||||
|
JOIN cursos c ON cf.curso_id = c.id
|
||||||
|
JOIN empresas e ON cf.empresa_id = e.id
|
||||||
|
JOIN anos_formaturas af ON cf.ano_formatura_id = af.id
|
||||||
|
JOIN tipos_eventos te ON a.tipo_evento_id = te.id
|
||||||
|
WHERE a.user_id = $1
|
||||||
|
ORDER BY a.data_evento;
|
||||||
|
|
||||||
-- name: UpdateAgenda :one
|
-- name: UpdateAgenda :one
|
||||||
UPDATE agenda
|
UPDATE agenda
|
||||||
SET
|
SET
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,19 @@ JOIN cursos cur ON c.curso_id = cur.id
|
||||||
JOIN anos_formaturas a ON c.ano_formatura_id = a.id
|
JOIN anos_formaturas a ON c.ano_formatura_id = a.id
|
||||||
ORDER BY c.fot DESC;
|
ORDER BY c.fot DESC;
|
||||||
|
|
||||||
|
-- name: ListCadastroFotByEmpresa :many
|
||||||
|
SELECT
|
||||||
|
c.*,
|
||||||
|
e.nome as empresa_nome,
|
||||||
|
cur.nome as curso_nome,
|
||||||
|
a.ano_semestre as ano_formatura_label
|
||||||
|
FROM cadastro_fot c
|
||||||
|
JOIN empresas e ON c.empresa_id = e.id
|
||||||
|
JOIN cursos cur ON c.curso_id = cur.id
|
||||||
|
JOIN anos_formaturas a ON c.ano_formatura_id = a.id
|
||||||
|
WHERE c.empresa_id = $1
|
||||||
|
ORDER BY c.fot DESC;
|
||||||
|
|
||||||
-- name: GetCadastroFotByID :one
|
-- name: GetCadastroFotByID :one
|
||||||
SELECT
|
SELECT
|
||||||
c.*,
|
c.*,
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,22 @@ VALUES ($1, $2, $3, false)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: GetUsuarioByEmail :one
|
-- name: GetUsuarioByEmail :one
|
||||||
SELECT * FROM usuarios
|
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
WHERE email = $1 LIMIT 1;
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
|
e.id as empresa_id,
|
||||||
|
e.nome as empresa_nome
|
||||||
|
FROM usuarios u
|
||||||
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
|
LEFT JOIN cadastro_clientes cc ON u.id = cc.usuario_id
|
||||||
|
LEFT JOIN empresas e ON cc.empresa_id = e.id
|
||||||
|
WHERE u.email = $1 LIMIT 1;
|
||||||
|
|
||||||
-- name: GetUsuarioByID :one
|
-- name: GetUsuarioByID :one
|
||||||
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
SELECT u.id, u.email, u.senha_hash, u.role, u.ativo, u.criado_em, u.atualizado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
|
e.id as empresa_id,
|
||||||
e.nome as empresa_nome
|
e.nome as empresa_nome
|
||||||
FROM usuarios u
|
FROM usuarios u
|
||||||
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
|
|
@ -26,6 +35,7 @@ WHERE id = $1;
|
||||||
SELECT u.id, u.email, u.role, u.ativo, u.criado_em,
|
SELECT u.id, u.email, u.role, u.ativo, u.criado_em,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
COALESCE(cp.whatsapp, cc.telefone, '') as whatsapp,
|
||||||
|
e.id as empresa_id,
|
||||||
e.nome as empresa_nome
|
e.nome as empresa_nome
|
||||||
FROM usuarios u
|
FROM usuarios u
|
||||||
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,7 @@ CREATE TABLE IF NOT EXISTS cadastro_clientes (
|
||||||
-- Agenda Table
|
-- Agenda Table
|
||||||
CREATE TABLE IF NOT EXISTS agenda (
|
CREATE TABLE IF NOT EXISTS agenda (
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
user_id UUID REFERENCES usuarios(id) ON DELETE CASCADE, -- User who created the event (Client/Owner)
|
||||||
fot_id UUID NOT NULL REFERENCES cadastro_fot(id) ON DELETE CASCADE,
|
fot_id UUID NOT NULL REFERENCES cadastro_fot(id) ON DELETE CASCADE,
|
||||||
data_evento DATE NOT NULL,
|
data_evento DATE NOT NULL,
|
||||||
tipo_evento_id UUID NOT NULL REFERENCES tipos_eventos(id),
|
tipo_evento_id UUID NOT NULL REFERENCES tipos_eventos(id),
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import { useData } from "../contexts/DataContext";
|
||||||
import { UserRole } from "../types";
|
import { UserRole } from "../types";
|
||||||
import { InstitutionForm } from "./InstitutionForm";
|
import { InstitutionForm } from "./InstitutionForm";
|
||||||
import { MapboxMap } from "./MapboxMap";
|
import { MapboxMap } from "./MapboxMap";
|
||||||
import { getEventTypes, EventTypeResponse } from "../services/apiService";
|
import { getEventTypes, EventTypeResponse, getCadastroFot, createAgenda } from "../services/apiService";
|
||||||
|
|
||||||
interface EventFormProps {
|
interface EventFormProps {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
|
@ -39,12 +39,9 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
initialData,
|
initialData,
|
||||||
}) => {
|
}) => {
|
||||||
const { user } = useAuth();
|
const { user, token: userToken } = useAuth();
|
||||||
const {
|
const {
|
||||||
institutions,
|
|
||||||
getInstitutionsByUserId,
|
|
||||||
addInstitution,
|
addInstitution,
|
||||||
getActiveCoursesByInstitutionId,
|
|
||||||
} = useData();
|
} = useData();
|
||||||
const [activeTab, setActiveTab] = useState<
|
const [activeTab, setActiveTab] = useState<
|
||||||
"details" | "location" | "briefing" | "files"
|
"details" | "location" | "briefing" | "files"
|
||||||
|
|
@ -55,19 +52,11 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
const [isGeocoding, setIsGeocoding] = useState(false);
|
const [isGeocoding, setIsGeocoding] = useState(false);
|
||||||
const [showToast, setShowToast] = useState(false);
|
const [showToast, setShowToast] = useState(false);
|
||||||
const [showInstitutionForm, setShowInstitutionForm] = useState(false);
|
const [showInstitutionForm, setShowInstitutionForm] = useState(false);
|
||||||
const [availableCourses, setAvailableCourses] = useState<any[]>([]);
|
|
||||||
const [eventTypes, setEventTypes] = useState<EventTypeResponse[]>([]);
|
const [eventTypes, setEventTypes] = useState<EventTypeResponse[]>([]);
|
||||||
const [isBackendDown, setIsBackendDown] = useState(false);
|
const [isBackendDown, setIsBackendDown] = useState(false);
|
||||||
const [isLoadingEventTypes, setIsLoadingEventTypes] = useState(true);
|
const [isLoadingEventTypes, setIsLoadingEventTypes] = useState(true);
|
||||||
|
|
||||||
// Get institutions based on user role
|
|
||||||
// Business owners and admins see all institutions, clients see only their own
|
|
||||||
const userInstitutions = user
|
|
||||||
? user.role === UserRole.BUSINESS_OWNER || user.role === UserRole.SUPERADMIN
|
|
||||||
? institutions
|
|
||||||
: getInstitutionsByUserId(user.id)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Default State or Initial Data
|
// Default State or Initial Data
|
||||||
const [formData, setFormData] = useState(
|
const [formData, setFormData] = useState(
|
||||||
initialData || {
|
initialData || {
|
||||||
|
|
@ -78,6 +67,7 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
endTime: "",
|
endTime: "",
|
||||||
type: "",
|
type: "",
|
||||||
status: EventStatus.PLANNING,
|
status: EventStatus.PLANNING,
|
||||||
|
locationName: "", // New field: Nome do Local
|
||||||
address: {
|
address: {
|
||||||
street: "",
|
street: "",
|
||||||
number: "",
|
number: "",
|
||||||
|
|
@ -91,9 +81,10 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
briefing: "",
|
briefing: "",
|
||||||
contacts: [{ name: "", role: "", phone: "" }],
|
contacts: [{ name: "", role: "", phone: "" }],
|
||||||
files: [] as File[],
|
files: [] as File[],
|
||||||
institutionId: "",
|
institutionId: "", // Legacy, might clear or keep for compatibility
|
||||||
attendees: "",
|
attendees: "",
|
||||||
courseId: "",
|
courseId: "", // Legacy
|
||||||
|
fotId: "", // New field for FOT linkage
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -104,7 +95,13 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
? "Solicitar Orçamento/Evento"
|
? "Solicitar Orçamento/Evento"
|
||||||
: "Cadastrar Novo Evento";
|
: "Cadastrar Novo Evento";
|
||||||
|
|
||||||
// Buscar tipos de eventos do backend
|
const submitLabel = initialData
|
||||||
|
? "Salvar Alterações"
|
||||||
|
: isClientRequest
|
||||||
|
? "Enviar Solicitação"
|
||||||
|
: "Criar Evento";
|
||||||
|
|
||||||
|
// Fetch Event Types
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchEventTypes = async () => {
|
const fetchEventTypes = async () => {
|
||||||
setIsLoadingEventTypes(true);
|
setIsLoadingEventTypes(true);
|
||||||
|
|
@ -123,25 +120,63 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
|
|
||||||
fetchEventTypes();
|
fetchEventTypes();
|
||||||
}, []);
|
}, []);
|
||||||
const submitLabel = initialData
|
|
||||||
? "Salvar Alterações"
|
|
||||||
: isClientRequest
|
|
||||||
? "Enviar Solicitação"
|
|
||||||
: "Criar Evento";
|
|
||||||
|
|
||||||
// Carregar cursos disponíveis quando instituição for selecionada
|
// Fetch FOTs filtered by user company
|
||||||
|
const [availableFots, setAvailableFots] = useState<any[]>([]);
|
||||||
|
const [loadingFots, setLoadingFots] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData.institutionId) {
|
const loadFots = async () => {
|
||||||
const courses = getActiveCoursesByInstitutionId(formData.institutionId);
|
// Allow FOT loading for Business Owners, Event Owners (Clients), and Superadmins
|
||||||
setAvailableCourses(courses);
|
if (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.EVENT_OWNER || user?.role === UserRole.SUPERADMIN) {
|
||||||
} else {
|
// If user is not superadmin (admin generally has no empresaId but sees all or selects one, here we assume superadmin logic is separate or allowed)
|
||||||
setAvailableCourses([]);
|
// Check if regular user has empresaId
|
||||||
// Limpa o curso selecionado se a instituição mudar
|
if (user?.role !== UserRole.SUPERADMIN && !user?.empresaId) {
|
||||||
if (formData.courseId) {
|
// If no company linked, do not load FOTs
|
||||||
setFormData((prev: any) => ({ ...prev, courseId: "" }));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingFots(true);
|
||||||
|
const token = localStorage.getItem("token") || "";
|
||||||
|
// Use empresaId from user context if available
|
||||||
|
const empresaId = user.empresaId;
|
||||||
|
const response = await getCadastroFot(token, empresaId);
|
||||||
|
|
||||||
|
if (response.data) {
|
||||||
|
// If we didn't filter by API (e.g. no empresaId), filter client side as fallback
|
||||||
|
const myFots = (empresaId || user.companyName)
|
||||||
|
? response.data.filter(f =>
|
||||||
|
(empresaId && f.empresa_id === empresaId) ||
|
||||||
|
(user.companyName && f.empresa_nome === user.companyName)
|
||||||
|
)
|
||||||
|
: response.data;
|
||||||
|
|
||||||
|
setAvailableFots(myFots);
|
||||||
|
}
|
||||||
|
setLoadingFots(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}, [formData.institutionId, getActiveCoursesByInstitutionId]);
|
loadFots();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
// Derived state for dropdowns
|
||||||
|
const [selectedCourseName, setSelectedCourseName] = useState("");
|
||||||
|
const [selectedInstitutionName, setSelectedInstitutionName] = useState("");
|
||||||
|
|
||||||
|
// Unique Courses
|
||||||
|
const uniqueCourses = Array.from(new Set(availableFots.map(f => f.curso_nome))).sort();
|
||||||
|
|
||||||
|
// Filtered Institutions based on Course
|
||||||
|
const filteredInstitutions = availableFots
|
||||||
|
.filter(f => f.curso_nome === selectedCourseName)
|
||||||
|
.map(f => f.instituicao)
|
||||||
|
.filter((v, i, a) => a.indexOf(v) === i)
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
// Filtered Years based on Course + Inst
|
||||||
|
const filteredYears = availableFots
|
||||||
|
.filter(f => f.curso_nome === selectedCourseName && f.instituicao === selectedInstitutionName)
|
||||||
|
.map(f => ({ id: f.id, label: f.ano_formatura_label }));
|
||||||
|
|
||||||
// Address Autocomplete Logic using Mapbox
|
// Address Autocomplete Logic using Mapbox
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -177,7 +212,6 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMapLocationChange = async (lat: number, lng: number) => {
|
const handleMapLocationChange = async (lat: number, lng: number) => {
|
||||||
// Buscar endereço baseado nas coordenadas
|
|
||||||
const addressData = await reverseGeocode(lat, lng);
|
const addressData = await reverseGeocode(lat, lng);
|
||||||
|
|
||||||
if (addressData) {
|
if (addressData) {
|
||||||
|
|
@ -195,7 +229,6 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// Se não conseguir o endereço, atualiza apenas as coordenadas
|
|
||||||
setFormData((prev: any) => ({
|
setFormData((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
address: {
|
address: {
|
||||||
|
|
@ -208,23 +241,16 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Geocoding quando o usuário digita o endereço manualmente
|
|
||||||
const handleManualAddressChange = async () => {
|
const handleManualAddressChange = async () => {
|
||||||
const { street, number, city, state } = formData.address;
|
const { street, number, city, state } = formData.address;
|
||||||
|
|
||||||
// Montar query de busca
|
|
||||||
const query = `${street} ${number}, ${city}, ${state}`.trim();
|
const query = `${street} ${number}, ${city}, ${state}`.trim();
|
||||||
|
if (query.length < 5) return;
|
||||||
if (query.length < 5) return; // Endereço muito curto
|
|
||||||
|
|
||||||
setIsGeocoding(true);
|
setIsGeocoding(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await searchMapboxLocation(query);
|
const results = await searchMapboxLocation(query);
|
||||||
|
|
||||||
if (results.length > 0) {
|
if (results.length > 0) {
|
||||||
const firstResult = results[0];
|
const firstResult = results[0];
|
||||||
|
|
||||||
setFormData((prev: any) => ({
|
setFormData((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
address: {
|
address: {
|
||||||
|
|
@ -264,25 +290,72 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = async () => {
|
||||||
// Validate institution selection
|
// Validation
|
||||||
if (!formData.institutionId) {
|
if (!formData.name) return alert("Preencha o tipo de evento");
|
||||||
alert("Por favor, selecione uma instituição antes de continuar.");
|
if (!formData.date) return alert("Preencha a data");
|
||||||
return;
|
if (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.EVENT_OWNER) {
|
||||||
|
if (!formData.fotId) {
|
||||||
|
alert("Por favor, selecione a Turma (Cadastro FOT) antes de continuar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate course selection
|
try {
|
||||||
if (!formData.courseId) {
|
setShowToast(true);
|
||||||
alert("Por favor, selecione um curso/turma antes de continuar.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show toast
|
// Prepare Payload for Agenda API
|
||||||
setShowToast(true);
|
const payload = {
|
||||||
// Call original submit after small delay for visual effect or immediately
|
fot_id: formData.fotId,
|
||||||
setTimeout(() => {
|
tipo_evento_id: formData.typeId || "00000000-0000-0000-0000-000000000000",
|
||||||
onSubmit(formData);
|
data_evento: new Date(formData.date).toISOString(),
|
||||||
}, 1000);
|
horario: formData.startTime || "",
|
||||||
|
observacoes_evento: formData.briefing || "",
|
||||||
|
local_evento: formData.locationName || "",
|
||||||
|
endereco: `${formData.address.street}, ${formData.address.number} - ${formData.address.city}/${formData.address.state}`,
|
||||||
|
qtd_formandos: parseInt(formData.attendees) || 0,
|
||||||
|
|
||||||
|
// Default integer values
|
||||||
|
qtd_fotografos: 0,
|
||||||
|
qtd_recepcionistas: 0,
|
||||||
|
qtd_cinegrafistas: 0,
|
||||||
|
qtd_estudios: 0,
|
||||||
|
qtd_ponto_foto: 0,
|
||||||
|
qtd_ponto_id: 0,
|
||||||
|
qtd_ponto_decorado: 0,
|
||||||
|
qtd_pontos_led: 0,
|
||||||
|
qtd_plataforma_360: 0,
|
||||||
|
|
||||||
|
status_profissionais: "PENDING",
|
||||||
|
foto_faltante: 0,
|
||||||
|
recep_faltante: 0,
|
||||||
|
cine_faltante: 0,
|
||||||
|
logistica_observacoes: "",
|
||||||
|
pre_venda: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const authToken = userToken || localStorage.getItem("token") || "";
|
||||||
|
const response = await createAgenda(authToken, payload);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
alert("Erro ao criar evento: " + response.error);
|
||||||
|
setShowToast(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (onSubmit) {
|
||||||
|
onSubmit(formData);
|
||||||
|
}
|
||||||
|
// Redirect or close is handled by parent, but we show success via toast usually
|
||||||
|
alert("Solicitação enviada com sucesso!");
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
alert("Erro inesperado: " + e.message);
|
||||||
|
setShowToast(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInstitutionSubmit = (institutionData: any) => {
|
const handleInstitutionSubmit = (institutionData: any) => {
|
||||||
|
|
@ -292,11 +365,10 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
ownerId: user?.id || "",
|
ownerId: user?.id || "",
|
||||||
};
|
};
|
||||||
addInstitution(newInstitution);
|
addInstitution(newInstitution);
|
||||||
setFormData((prev) => ({ ...prev, institutionId: newInstitution.id }));
|
setFormData((prev: any) => ({ ...prev, institutionId: newInstitution.id }));
|
||||||
setShowInstitutionForm(false);
|
setShowInstitutionForm(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show institution form modal
|
|
||||||
if (showInstitutionForm) {
|
if (showInstitutionForm) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||||
|
|
@ -337,7 +409,7 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
: "Preencha as informações técnicas do evento."}
|
: "Preencha as informações técnicas do evento."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Step indicators - Hidden on mobile, shown on tablet+ */}
|
{/* Step indicators */}
|
||||||
<div className="hidden sm:flex space-x-2">
|
<div className="hidden sm:flex space-x-2">
|
||||||
{["details", "location", "briefing", "files"].map((tab, idx) => (
|
{["details", "location", "briefing", "files"].map((tab, idx) => (
|
||||||
<div
|
<div
|
||||||
|
|
@ -359,7 +431,7 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Tabs - Horizontal */}
|
{/* Mobile Tabs */}
|
||||||
<div className="lg:hidden border-b border-gray-200 bg-white overflow-x-auto scrollbar-hide">
|
<div className="lg:hidden border-b border-gray-200 bg-white overflow-x-auto scrollbar-hide">
|
||||||
<div className="flex min-w-max">
|
<div className="flex min-w-max">
|
||||||
{[
|
{[
|
||||||
|
|
@ -380,11 +452,9 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
: "text-gray-500 border-transparent hover:bg-gray-50"
|
: "text-gray-500 border-transparent hover:bg-gray-50"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span className="inline-block w-5 h-5 rounded-full text-[10px] leading-5 text-center mr-1.5 ${
|
||||||
className="inline-block w-5 h-5 rounded-full text-[10px] leading-5 text-center mr-1.5 ${
|
|
||||||
activeTab === item.id ? 'bg-brand-gold text-white' : 'bg-gray-200 text-gray-600'
|
activeTab === item.id ? 'bg-brand-gold text-white' : 'bg-gray-200 text-gray-600'
|
||||||
}"
|
}">
|
||||||
>
|
|
||||||
{item.icon}
|
{item.icon}
|
||||||
</span>
|
</span>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|
@ -429,14 +499,22 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
required
|
required
|
||||||
value={formData.type}
|
value={formData.typeId || ""}
|
||||||
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
|
onChange={(e) => {
|
||||||
|
const selectedId = e.target.value;
|
||||||
|
const selectedType = eventTypes.find((t) => t.id === selectedId);
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
typeId: selectedId,
|
||||||
|
type: selectedType?.nome || ""
|
||||||
|
});
|
||||||
|
}}
|
||||||
disabled={isLoadingEventTypes || isBackendDown}
|
disabled={isLoadingEventTypes || isBackendDown}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold disabled:bg-gray-100 disabled:cursor-not-allowed"
|
className="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-gold disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<option value="">Selecione o tipo de evento</option>
|
<option value="">Selecione o tipo de evento</option>
|
||||||
{eventTypes.map((type) => (
|
{eventTypes.map((type) => (
|
||||||
<option key={type.id} value={type.nome}>
|
<option key={type.id} value={type.id}>
|
||||||
{type.nome}
|
{type.nome}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
|
@ -455,8 +533,8 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label="Nome do Evento (Opcional)"
|
label="Observações do Evento (Opcional)"
|
||||||
placeholder="Ex: Formatura Educação Física 2025"
|
placeholder="Ex: Cerimônia de Colação de Grau"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, name: e.target.value })
|
setFormData({ ...formData, name: e.target.value })
|
||||||
|
|
@ -482,23 +560,21 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
label="Horário de Término*"
|
label="Horário de Término (Opcional)"
|
||||||
type="time"
|
type="time"
|
||||||
value={formData.endTime}
|
value={formData.endTime}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData({ ...formData, endTime: e.target.value })
|
setFormData({ ...formData, endTime: e.target.value })
|
||||||
}
|
}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label="Número de universitários"
|
label="Número de Formandos"
|
||||||
placeholder="Ex: 150"
|
placeholder="Ex: 50"
|
||||||
value={formData.attendees}
|
value={formData.attendees}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
// Permite apenas números
|
|
||||||
if (value === "" || /^\d+$/.test(value)) {
|
if (value === "" || /^\d+$/.test(value)) {
|
||||||
setFormData({ ...formData, attendees: value });
|
setFormData({ ...formData, attendees: value });
|
||||||
}
|
}
|
||||||
|
|
@ -507,137 +583,85 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Institution Selection - OBRIGATÓRIO */}
|
{/* Dynamic FOT Selection */}
|
||||||
<div>
|
<div className="bg-gray-50 p-4 rounded-md border border-gray-200">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
|
<h3 className="text-sm font-medium text-gray-700 mb-4 uppercase tracking-wider">Seleção da Turma</h3>
|
||||||
Universidade*{" "}
|
|
||||||
<span className="text-brand-gold">(Obrigatório)</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{userInstitutions.length === 0 ? (
|
{!user?.empresaId && user?.role !== UserRole.SUPERADMIN ? (
|
||||||
<div className="border-2 border-dashed border-amber-300 bg-amber-50 rounded-sm p-4">
|
<div className="bg-red-50 border-l-4 border-red-400 p-4 mb-4">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex">
|
||||||
<AlertCircle
|
<div className="flex-shrink-0">
|
||||||
className="text-amber-600 flex-shrink-0 mt-0.5"
|
<AlertCircle className="h-5 w-5 text-red-400" aria-hidden="true" />
|
||||||
size={20}
|
</div>
|
||||||
/>
|
<div className="ml-3">
|
||||||
<div className="flex-1">
|
<p className="text-sm text-red-700">
|
||||||
<p className="text-sm font-medium text-amber-900 mb-2">
|
Sua conta não está vinculada a nenhuma empresa. Por favor, entre em contato com a administração para regularizar seu cadastro antes de solicitar um evento.
|
||||||
Nenhuma universidade cadastrada
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-amber-700 mb-3">
|
|
||||||
Você precisa cadastrar uma universidade antes de
|
|
||||||
criar um evento. Trabalhamos exclusivamente com
|
|
||||||
eventos fotográficos em universidades.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowInstitutionForm(true)}
|
|
||||||
className="text-xs font-bold text-amber-900 hover:text-amber-700 underline flex items-center"
|
|
||||||
>
|
|
||||||
<Plus size={14} className="mr-1" />
|
|
||||||
Cadastrar minha primeira universidade
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<>
|
||||||
<select
|
{/* 1. Curso */}
|
||||||
className="w-full px-4 py-2 border border-gray-300 rounded-sm focus:outline-none focus:ring-1 focus:ring-brand-gold focus:border-brand-gold transition-colors"
|
<div className="mb-4">
|
||||||
value={formData.institutionId}
|
<label className="block text-sm text-gray-600 mb-1">Curso</label>
|
||||||
onChange={(e) =>
|
<select
|
||||||
setFormData({
|
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-brand-gold focus:border-brand-gold"
|
||||||
...formData,
|
value={selectedCourseName}
|
||||||
institutionId: e.target.value,
|
onChange={e => {
|
||||||
})
|
setSelectedCourseName(e.target.value);
|
||||||
}
|
setSelectedInstitutionName("");
|
||||||
required
|
setFormData({ ...formData, fotId: "" });
|
||||||
>
|
}}
|
||||||
<option value="">Selecione uma universidade</option>
|
disabled={loadingFots}
|
||||||
{userInstitutions.map((inst) => (
|
>
|
||||||
<option key={inst.id} value={inst.id}>
|
<option value="">Selecione o Curso</option>
|
||||||
{inst.name} - {inst.type}
|
{uniqueCourses.map(c => <option key={c} value={c}>{c}</option>)}
|
||||||
</option>
|
</select>
|
||||||
))}
|
</div>
|
||||||
</select>
|
|
||||||
|
|
||||||
<button
|
{/* 2. Instituição */}
|
||||||
type="button"
|
<div className="mb-4">
|
||||||
onClick={() => setShowInstitutionForm(true)}
|
<label className="block text-sm text-gray-600 mb-1">Instituição</label>
|
||||||
className="text-xs text-brand-gold hover:underline flex items-center"
|
<select
|
||||||
>
|
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-brand-gold focus:border-brand-gold"
|
||||||
<Plus size={12} className="mr-1" />
|
value={selectedInstitutionName}
|
||||||
Cadastrar nova universidade
|
onChange={e => {
|
||||||
</button>
|
setSelectedInstitutionName(e.target.value);
|
||||||
|
setFormData({ ...formData, fotId: "" });
|
||||||
|
}}
|
||||||
|
disabled={!selectedCourseName}
|
||||||
|
>
|
||||||
|
<option value="">Selecione a Instituição</option>
|
||||||
|
{filteredInstitutions.map(i => <option key={i} value={i}>{i}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{formData.institutionId && (
|
{/* 3. Ano/Turma (Final FOT Selection) */}
|
||||||
<div className="bg-green-50 border border-green-200 rounded-sm p-3 flex items-center">
|
<div className="mb-0">
|
||||||
<Check size={16} className="text-green-600 mr-2" />
|
<label className="block text-sm text-gray-600 mb-1">Ano/Turma</label>
|
||||||
<span className="text-xs text-green-800">
|
<select
|
||||||
Universidade selecionada com sucesso
|
className="w-full px-3 py-2 border border-gray-300 rounded focus:ring-brand-gold focus:border-brand-gold"
|
||||||
</span>
|
value={formData.fotId || ""}
|
||||||
</div>
|
onChange={e => setFormData({ ...formData, fotId: e.target.value })}
|
||||||
)}
|
disabled={!selectedInstitutionName}
|
||||||
</div>
|
>
|
||||||
|
<option value="">Selecione a Turma</option>
|
||||||
|
{filteredYears.map(f => (
|
||||||
|
<option key={f.id} value={f.id}>{f.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Course Selection - Condicional baseado na instituição */}
|
|
||||||
{formData.institutionId && (
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
|
|
||||||
Curso/Turma <span className="text-red-500">*</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{availableCourses.length === 0 ? (
|
|
||||||
<div className="border-2 border-dashed border-gray-300 bg-gray-50 rounded-sm p-4">
|
|
||||||
<div className="flex items-start space-x-3">
|
|
||||||
<AlertCircle
|
|
||||||
className="text-gray-400 flex-shrink-0 mt-0.5"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="text-sm font-medium text-gray-700 mb-1">
|
|
||||||
Nenhum curso cadastrado
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-500">
|
|
||||||
Entre em contato com a administração para
|
|
||||||
cadastrar os cursos/turmas disponíveis nesta
|
|
||||||
universidade.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<select
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 rounded-sm focus:outline-none focus:ring-1 focus:ring-brand-gold focus:border-brand-gold transition-colors"
|
|
||||||
value={formData.courseId}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
courseId: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="">Selecione um curso *</option>
|
|
||||||
{availableCourses.map((course) => (
|
|
||||||
<option key={course.id} value={course.id}>
|
|
||||||
{course.name} - {course.graduationType} (
|
|
||||||
{course.year}/{course.semester}º sem)
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row justify-end gap-3 sm:gap-0 mt-8">
|
<div className="flex flex-col sm:flex-row justify-end gap-3 sm:gap-0 mt-8">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setActiveTab("location")}
|
onClick={() => setActiveTab("location")}
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
|
disabled={(!user?.empresaId && user?.role !== UserRole.SUPERADMIN)}
|
||||||
>
|
>
|
||||||
Próximo: Localização
|
Próximo: Localização
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -647,8 +671,17 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
|
|
||||||
{activeTab === "location" && (
|
{activeTab === "location" && (
|
||||||
<div className="space-y-6 fade-in">
|
<div className="space-y-6 fade-in">
|
||||||
<div className="relative">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
|
{/* Nome do Local */}
|
||||||
|
<Input
|
||||||
|
label="Nome do Local"
|
||||||
|
placeholder="Ex: Espaço das Américas, Salão de Festas X"
|
||||||
|
value={formData.locationName}
|
||||||
|
onChange={(e) => setFormData({ ...formData, locationName: e.target.value })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="bg-gray-50 p-4 rounded-lg border border-gray-200">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2 tracking-wide uppercase text-xs">
|
||||||
Busca de Endereço (Powered by Mapbox)
|
Busca de Endereço (Powered by Mapbox)
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|
@ -976,7 +1009,7 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div >
|
||||||
</div>
|
</div >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ export const EventTable: React.FC<EventTableProps> = ({
|
||||||
[EventStatus.CONFIRMED]: "Confirmado",
|
[EventStatus.CONFIRMED]: "Confirmado",
|
||||||
[EventStatus.PLANNING]: "Planejamento",
|
[EventStatus.PLANNING]: "Planejamento",
|
||||||
[EventStatus.IN_PROGRESS]: "Em Andamento",
|
[EventStatus.IN_PROGRESS]: "Em Andamento",
|
||||||
[EventStatus.COMPLETED]: "Concluído",
|
[EventStatus.DELIVERED]: "Entregue",
|
||||||
[EventStatus.ARCHIVED]: "Arquivado",
|
[EventStatus.ARCHIVED]: "Arquivado",
|
||||||
};
|
};
|
||||||
return statusLabels[status] || status;
|
return statusLabels[status] || status;
|
||||||
|
|
@ -232,7 +232,7 @@ export const EventTable: React.FC<EventTableProps> = ({
|
||||||
>
|
>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
{(event as any).fotId || "-"}
|
{event.fot || "-"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
|
|
@ -242,22 +242,22 @@ export const EventTable: React.FC<EventTableProps> = ({
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">
|
||||||
{(event as any).curso || "-"}
|
{event.curso || "-"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">
|
||||||
{(event as any).instituicao || "-"}
|
{event.instituicao || "-"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">
|
||||||
{(event as any).anoFormatura || "-"}
|
{event.anoFormatura || "-"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">
|
||||||
{(event as any).empresa || "-"}
|
{event.empresa || "-"}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
|
|
@ -265,9 +265,8 @@ export const EventTable: React.FC<EventTableProps> = ({
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
|
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${STATUS_COLORS[event.status] || "bg-gray-100 text-gray-800"
|
||||||
STATUS_COLORS[event.status] || "bg-gray-100 text-gray-800"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{getStatusDisplay(event.status)}
|
{getStatusDisplay(event.status)}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
name: backendUser.email.split('@')[0],
|
name: backendUser.email.split('@')[0],
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
};
|
};
|
||||||
if (!backendUser.ativo) {
|
if (!backendUser.ativo) {
|
||||||
console.warn("User is not active, logging out.");
|
console.warn("User is not active, logging out.");
|
||||||
|
|
@ -155,6 +157,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
name: backendUser.email.split('@')[0], // Fallback name or from profile if available
|
name: backendUser.email.split('@')[0], // Fallback name or from profile if available
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
// ... propagate other fields if needed or fetch profile
|
// ... propagate other fields if needed or fetch profile
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -227,6 +231,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
name: backendUser.nome || backendUser.email.split('@')[0],
|
name: backendUser.nome || backendUser.email.split('@')[0],
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
};
|
};
|
||||||
setUser(mappedUser);
|
setUser(mappedUser);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
|
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
|
||||||
|
import { useAuth } from "./AuthContext";
|
||||||
import { getPendingUsers, approveUser as apiApproveUser } from "../services/apiService";
|
import { getPendingUsers, approveUser as apiApproveUser } from "../services/apiService";
|
||||||
import {
|
import {
|
||||||
EventData,
|
EventData,
|
||||||
|
|
@ -607,15 +608,75 @@ interface DataContextType {
|
||||||
|
|
||||||
const DataContext = createContext<DataContextType | undefined>(undefined);
|
const DataContext = createContext<DataContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
|
||||||
export const DataProvider: React.FC<{ children: ReactNode }> = ({
|
export const DataProvider: React.FC<{ children: ReactNode }> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { token, user } = useAuth(); // Consume Auth Context
|
||||||
const [events, setEvents] = useState<EventData[]>(INITIAL_EVENTS);
|
const [events, setEvents] = useState<EventData[]>(INITIAL_EVENTS);
|
||||||
const [institutions, setInstitutions] =
|
const [institutions, setInstitutions] =
|
||||||
useState<Institution[]>(INITIAL_INSTITUTIONS);
|
useState<Institution[]>(INITIAL_INSTITUTIONS);
|
||||||
const [courses, setCourses] = useState<Course[]>(INITIAL_COURSES);
|
const [courses, setCourses] = useState<Course[]>(INITIAL_COURSES);
|
||||||
const [pendingUsers, setPendingUsers] = useState<User[]>([]);
|
const [pendingUsers, setPendingUsers] = useState<User[]>([]);
|
||||||
|
|
||||||
|
// Fetch events from API
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchEvents = async () => {
|
||||||
|
// Use token from context or fallback to localStorage if context not ready (though context is preferred sources of truth)
|
||||||
|
const visibleToken = token || localStorage.getItem("token");
|
||||||
|
|
||||||
|
if (visibleToken) {
|
||||||
|
try {
|
||||||
|
// Import dynamic to avoid circular dependency if any, or just use imported service
|
||||||
|
const { getAgendas } = await import("../services/apiService");
|
||||||
|
const result = await getAgendas(visibleToken);
|
||||||
|
if (result.data) {
|
||||||
|
const mappedEvents: EventData[] = result.data.map((e: any) => ({
|
||||||
|
id: e.id,
|
||||||
|
name: e.observacoes_evento || e.tipo_evento_nome || "Evento sem nome", // Fallback mapping
|
||||||
|
date: e.data_evento ? e.data_evento.split('T')[0] : "",
|
||||||
|
time: e.horario || "00:00",
|
||||||
|
type: (e.tipo_evento_nome || "Outro") as EventType, // Map string to enum if possible, or keep string
|
||||||
|
status: EventStatus.PENDING_APPROVAL, // Default or map from e.status_profissionais?? e.status_profissionais is for workers. We might need a status field on agenda table or infer it.
|
||||||
|
// For now, let's map status_profissionais to general status if possible, or default to CONFIRMED/PENDING
|
||||||
|
// e.status_profissionais defaults to "PENDING" in creation.
|
||||||
|
address: {
|
||||||
|
street: e.endereco ? e.endereco.split(',')[0] : "",
|
||||||
|
number: e.endereco ? e.endereco.split(',')[1]?.split('-')[0]?.trim() || "" : "",
|
||||||
|
city: e.endereco ? e.endereco.split('-')[1]?.split('/')[0]?.trim() || "" : "",
|
||||||
|
state: e.endereco ? e.endereco.split('/')[1]?.trim() || "" : "",
|
||||||
|
zip: "",
|
||||||
|
mapLink: e.local_evento.startsWith('http') ? e.local_evento : undefined
|
||||||
|
},
|
||||||
|
briefing: e.observacoes_evento || "",
|
||||||
|
coverImage: "https://picsum.photos/id/10/800/400", // Placeholder
|
||||||
|
contacts: [], // TODO: fetch contacts if needed
|
||||||
|
checklist: [],
|
||||||
|
ownerId: e.user_id || "unknown",
|
||||||
|
photographerIds: [], // TODO
|
||||||
|
institutionId: "", // TODO
|
||||||
|
attendees: e.qtd_formandos,
|
||||||
|
fotId: e.fot_id, // UUID
|
||||||
|
|
||||||
|
// Joined Fields
|
||||||
|
fot: e.fot_numero ?? e.fot_id, // Show Number if available (even 0), else ID
|
||||||
|
curso: e.curso_nome,
|
||||||
|
instituicao: e.instituicao,
|
||||||
|
anoFormatura: e.ano_semestre,
|
||||||
|
empresa: e.empresa_nome,
|
||||||
|
observacoes: e.observacoes_fot,
|
||||||
|
typeId: e.tipo_evento_id
|
||||||
|
}));
|
||||||
|
setEvents(mappedEvents);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch events", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchEvents();
|
||||||
|
}, [token]); // React to token change
|
||||||
|
|
||||||
// Fetch pending users from API
|
// Fetch pending users from API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
|
|
@ -653,8 +714,60 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const addEvent = (event: EventData) => {
|
const addEvent = async (event: EventData) => {
|
||||||
setEvents((prev) => [event, ...prev]);
|
const token = localStorage.getItem("@Photum:token");
|
||||||
|
if (!token) {
|
||||||
|
console.error("No token found");
|
||||||
|
// Fallback for offline/mock
|
||||||
|
setEvents((prev) => [event, ...prev]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Map frontend fields (camelCase) to backend fields (snake_case)
|
||||||
|
const payload = {
|
||||||
|
fot_id: event.fotId,
|
||||||
|
data_evento: event.date, // "YYYY-MM-DD" is acceptable
|
||||||
|
tipo_evento_id: event.typeId,
|
||||||
|
observacoes_evento: event.name, // "Observações do Evento" maps to name in EventForm
|
||||||
|
// local_evento: event.address.street + ", " + event.address.number, // Or map separate fields if needed
|
||||||
|
local_evento: event.address.mapLink || "Local a definir", // using mapLink or some string
|
||||||
|
endereco: `${event.address.street}, ${event.address.number}, ${event.address.city} - ${event.address.state}`,
|
||||||
|
horario: event.startTime,
|
||||||
|
// Defaulting missing counts to 0 for now as they are not in the simplified form
|
||||||
|
qtd_formandos: event.attendees ? parseInt(String(event.attendees)) : 0,
|
||||||
|
qtd_fotografos: 0,
|
||||||
|
qtd_recepcionistas: 0,
|
||||||
|
qtd_cinegrafistas: 0,
|
||||||
|
qtd_estudios: 0,
|
||||||
|
qtd_ponto_foto: 0,
|
||||||
|
qtd_ponto_id: 0,
|
||||||
|
qtd_ponto_decorado: 0,
|
||||||
|
qtd_pontos_led: 0,
|
||||||
|
qtd_plataforma_360: 0,
|
||||||
|
status_profissionais: "AGUARDANDO", // Will be calculated by backend anyway
|
||||||
|
foto_faltante: 0,
|
||||||
|
recep_faltante: 0,
|
||||||
|
cine_faltante: 0,
|
||||||
|
logistica_observacoes: "",
|
||||||
|
pre_venda: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await import("../services/apiService").then(m => m.createAgenda(token, payload));
|
||||||
|
|
||||||
|
if (result.data) {
|
||||||
|
// Success
|
||||||
|
console.log("Agenda criada:", result.data);
|
||||||
|
const newEvent = { ...event, id: result.data.id, status: EventStatus.PENDING_APPROVAL };
|
||||||
|
setEvents((prev) => [newEvent, ...prev]);
|
||||||
|
} else {
|
||||||
|
console.error("Erro ao criar agenda API:", result.error);
|
||||||
|
// Fallback or Toast?
|
||||||
|
// We will optimistically add it locally or throw
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Exception creating agenda:", err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateEventStatus = (id: string, status: EventStatus) => {
|
const updateEventStatus = (id: string, status: EventStatus) => {
|
||||||
|
|
|
||||||
|
|
@ -368,29 +368,27 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
|
|
||||||
{(user.role === UserRole.BUSINESS_OWNER ||
|
{(user.role === UserRole.BUSINESS_OWNER ||
|
||||||
user.role === UserRole.SUPERADMIN) && (
|
user.role === UserRole.SUPERADMIN) && (
|
||||||
<div className="flex space-x-2 bg-white p-1 rounded border border-gray-200">
|
<div className="flex space-x-2 bg-white p-1 rounded border border-gray-200">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveFilter("all")}
|
onClick={() => setActiveFilter("all")}
|
||||||
className={`px-3 py-1 text-xs font-medium rounded-sm ${
|
className={`px-3 py-1 text-xs font-medium rounded-sm ${activeFilter === "all"
|
||||||
activeFilter === "all"
|
? "bg-brand-black text-white"
|
||||||
? "bg-brand-black text-white"
|
: "text-gray-600 hover:bg-gray-100"
|
||||||
: "text-gray-600 hover:bg-gray-100"
|
}`}
|
||||||
}`}
|
>
|
||||||
>
|
Todos
|
||||||
Todos
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
onClick={() => setActiveFilter("pending")}
|
||||||
onClick={() => setActiveFilter("pending")}
|
className={`px-3 py-1 text-xs font-medium rounded-sm flex items-center ${activeFilter === "pending"
|
||||||
className={`px-3 py-1 text-xs font-medium rounded-sm flex items-center ${
|
? "bg-brand-gold text-white"
|
||||||
activeFilter === "pending"
|
: "text-gray-600 hover:bg-gray-100"
|
||||||
? "bg-brand-gold text-white"
|
}`}
|
||||||
: "text-gray-600 hover:bg-gray-100"
|
>
|
||||||
}`}
|
<Clock size={12} className="mr-1" /> Pendentes
|
||||||
>
|
</button>
|
||||||
<Clock size={12} className="mr-1" /> Pendentes
|
</div>
|
||||||
</button>
|
)}
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Advanced Filters */}
|
{/* Advanced Filters */}
|
||||||
|
|
@ -481,9 +479,8 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`px-4 py-2 rounded text-sm font-semibold ${
|
className={`px-4 py-2 rounded text-sm font-semibold ${STATUS_COLORS[selectedEvent.status]
|
||||||
STATUS_COLORS[selectedEvent.status]
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{selectedEvent.status}
|
{selectedEvent.status}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -495,23 +492,23 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
<div className="flex flex-wrap gap-2 mb-6 pb-4 border-b">
|
<div className="flex flex-wrap gap-2 mb-6 pb-4 border-b">
|
||||||
{(user.role === UserRole.BUSINESS_OWNER ||
|
{(user.role === UserRole.BUSINESS_OWNER ||
|
||||||
user.role === UserRole.SUPERADMIN) && (
|
user.role === UserRole.SUPERADMIN) && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setView("edit")}
|
onClick={() => setView("edit")}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
>
|
>
|
||||||
<Edit size={16} className="mr-2" /> Editar Detalhes
|
<Edit size={16} className="mr-2" /> Editar Detalhes
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleManageTeam}
|
onClick={handleManageTeam}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
>
|
>
|
||||||
<Users size={16} className="mr-2" /> Gerenciar Equipe
|
<Users size={16} className="mr-2" /> Gerenciar Equipe
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{user.role === UserRole.EVENT_OWNER &&
|
{user.role === UserRole.EVENT_OWNER &&
|
||||||
selectedEvent.status !== EventStatus.ARCHIVED && (
|
selectedEvent.status !== EventStatus.ARCHIVED && (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -578,7 +575,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
FOT
|
FOT
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900 font-medium">
|
<td className="px-4 py-3 text-sm text-gray-900 font-medium">
|
||||||
{(selectedEvent as any).fotId || "-"}
|
{(selectedEvent as any).fot || "-"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-gray-50">
|
<tr className="hover:bg-gray-50">
|
||||||
|
|
@ -596,7 +593,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
Curso
|
Curso
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900">
|
<td className="px-4 py-3 text-sm text-gray-900">
|
||||||
{(selectedEvent as any).curso || "-"}
|
{selectedEvent.curso || "-"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-gray-50">
|
<tr className="hover:bg-gray-50">
|
||||||
|
|
@ -604,7 +601,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
Instituição
|
Instituição
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900">
|
<td className="px-4 py-3 text-sm text-gray-900">
|
||||||
{(selectedEvent as any).instituicao || "-"}
|
{selectedEvent.instituicao || "-"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-gray-50">
|
<tr className="hover:bg-gray-50">
|
||||||
|
|
@ -612,7 +609,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
Ano Formatura
|
Ano Formatura
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900">
|
<td className="px-4 py-3 text-sm text-gray-900">
|
||||||
{(selectedEvent as any).anoFormatura || "-"}
|
{selectedEvent.anoFormatura || "-"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-gray-50">
|
<tr className="hover:bg-gray-50">
|
||||||
|
|
@ -620,7 +617,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
Empresa
|
Empresa
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900">
|
<td className="px-4 py-3 text-sm text-gray-900">
|
||||||
{(selectedEvent as any).empresa || "-"}
|
{selectedEvent.empresa || "-"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="hover:bg-gray-50">
|
<tr className="hover:bg-gray-50">
|
||||||
|
|
@ -672,7 +669,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
Qtd Formandos
|
Qtd Formandos
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900">
|
<td className="px-4 py-3 text-sm text-gray-900">
|
||||||
{(selectedEvent as any).qtdFormandos || "-"}
|
{selectedEvent.attendees || "-"}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -834,59 +831,58 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
{(selectedEvent.photographerIds.length > 0 ||
|
{(selectedEvent.photographerIds.length > 0 ||
|
||||||
user.role === UserRole.BUSINESS_OWNER ||
|
user.role === UserRole.BUSINESS_OWNER ||
|
||||||
user.role === UserRole.SUPERADMIN) && (
|
user.role === UserRole.SUPERADMIN) && (
|
||||||
<div className="border p-5 rounded bg-white">
|
<div className="border p-5 rounded bg-white">
|
||||||
<div className="flex justify-between items-center mb-3">
|
<div className="flex justify-between items-center mb-3">
|
||||||
<h4 className="font-bold text-sm text-gray-700 flex items-center gap-2">
|
<h4 className="font-bold text-sm text-gray-700 flex items-center gap-2">
|
||||||
<Users size={16} className="text-brand-gold" />
|
<Users size={16} className="text-brand-gold" />
|
||||||
Equipe ({selectedEvent.photographerIds.length})
|
Equipe ({selectedEvent.photographerIds.length})
|
||||||
</h4>
|
</h4>
|
||||||
{(user.role === UserRole.BUSINESS_OWNER ||
|
{(user.role === UserRole.BUSINESS_OWNER ||
|
||||||
user.role === UserRole.SUPERADMIN) && (
|
user.role === UserRole.SUPERADMIN) && (
|
||||||
<button
|
<button
|
||||||
onClick={handleManageTeam}
|
onClick={handleManageTeam}
|
||||||
className="text-brand-gold hover:text-brand-black transition-colors"
|
className="text-brand-gold hover:text-brand-black transition-colors"
|
||||||
title="Adicionar fotógrafo"
|
title="Adicionar fotógrafo"
|
||||||
>
|
>
|
||||||
<PlusCircle size={18} />
|
<PlusCircle size={18} />
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedEvent.photographerIds.length > 0 ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{selectedEvent.photographerIds.map((id) => {
|
||||||
|
const photographer = MOCK_PHOTOGRAPHERS.find(
|
||||||
|
(p) => p.id === id
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
className="flex items-center gap-2 text-sm"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-8 h-8 rounded-full border-2 border-gray-200 bg-gray-300 flex-shrink-0"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${photographer?.avatar ||
|
||||||
|
`https://i.pravatar.cc/100?u=${id}`
|
||||||
|
})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<span className="text-gray-700">
|
||||||
|
{photographer?.name || id}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-400 italic">
|
||||||
|
Nenhum profissional atribuído
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{selectedEvent.photographerIds.length > 0 ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{selectedEvent.photographerIds.map((id) => {
|
|
||||||
const photographer = MOCK_PHOTOGRAPHERS.find(
|
|
||||||
(p) => p.id === id
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={id}
|
|
||||||
className="flex items-center gap-2 text-sm"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-8 h-8 rounded-full border-2 border-gray-200 bg-gray-300 flex-shrink-0"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url(${
|
|
||||||
photographer?.avatar ||
|
|
||||||
`https://i.pravatar.cc/100?u=${id}`
|
|
||||||
})`,
|
|
||||||
backgroundSize: "cover",
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<span className="text-gray-700">
|
|
||||||
{photographer?.name || id}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-gray-400 italic">
|
|
||||||
Nenhum profissional atribuído
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1034,13 +1030,12 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
togglePhotographer(photographer.id)
|
togglePhotographer(photographer.id)
|
||||||
}
|
}
|
||||||
disabled={!isAvailable && !isAssigned}
|
disabled={!isAvailable && !isAssigned}
|
||||||
className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors ${
|
className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors ${isAssigned
|
||||||
isAssigned
|
|
||||||
? "bg-red-100 text-red-700 hover:bg-red-200"
|
? "bg-red-100 text-red-700 hover:bg-red-200"
|
||||||
: isAvailable
|
: isAvailable
|
||||||
? "bg-brand-gold text-white hover:bg-[#a5bd2e]"
|
? "bg-brand-gold text-white hover:bg-[#a5bd2e]"
|
||||||
: "bg-gray-100 text-gray-400 cursor-not-allowed"
|
: "bg-gray-100 text-gray-400 cursor-not-allowed"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isAssigned ? "Remover" : "Adicionar"}
|
{isAssigned ? "Remover" : "Adicionar"}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1055,21 +1050,21 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
p.availability[selectedEvent.date] ?? false;
|
p.availability[selectedEvent.date] ?? false;
|
||||||
return isAvailable || isAssigned;
|
return isAvailable || isAssigned;
|
||||||
}).length === 0 && (
|
}).length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={5} className="p-8 text-center">
|
<td colSpan={5} className="p-8 text-center">
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<UserX size={48} className="text-gray-300" />
|
<UserX size={48} className="text-gray-300" />
|
||||||
<p className="text-gray-500 font-medium">
|
<p className="text-gray-500 font-medium">
|
||||||
Nenhum profissional disponível para esta data
|
Nenhum profissional disponível para esta data
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-400">
|
<p className="text-sm text-gray-400">
|
||||||
Tente selecionar outra data ou entre em contato
|
Tente selecionar outra data ou entre em contato
|
||||||
com a equipe
|
com a equipe
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,17 @@ export async function getAvailableCourses(): Promise<ApiResponse<Array<{ id: str
|
||||||
/**
|
/**
|
||||||
* Busca a listagem de Cadastro FOT
|
* Busca a listagem de Cadastro FOT
|
||||||
*/
|
*/
|
||||||
export async function getCadastroFot(token: string): Promise<ApiResponse<any[]>> {
|
/**
|
||||||
|
* Busca a listagem de Cadastro FOT
|
||||||
|
*/
|
||||||
|
export async function getCadastroFot(token: string, empresaId?: string): Promise<ApiResponse<any[]>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/cadastro-fot`, {
|
let url = `${API_BASE_URL}/api/cadastro-fot`;
|
||||||
|
if (empresaId) {
|
||||||
|
url += `?empresa_id=${empresaId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
@ -270,7 +278,55 @@ export async function getUniversities(): Promise<
|
||||||
return fetchFromBackend("/api/universidades");
|
return fetchFromBackend("/api/universidades");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... existing functions ...
|
// Agenda
|
||||||
|
export const createAgenda = async (token: string, data: any) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/agenda`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await response.json();
|
||||||
|
return { data: responseData, error: null };
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Erro ao criar agenda:", error);
|
||||||
|
return { data: null, error: error.message || "Erro ao criar agenda" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Agenda
|
||||||
|
export const getAgendas = async (token: string): Promise<ApiResponse<any[]>> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/agenda`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${token}`
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return { data, error: null, isBackendDown: false };
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Erro ao buscar agendas:", error);
|
||||||
|
return { data: null, error: error.message || "Erro ao buscar agendas", isBackendDown: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Busca usuários pendentes de aprovação
|
* Busca usuários pendentes de aprovação
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ export interface User {
|
||||||
phone?: string; // Telefone do usuário
|
phone?: string; // Telefone do usuário
|
||||||
createdAt?: string; // Data de criação do cadastro
|
createdAt?: string; // Data de criação do cadastro
|
||||||
ativo?: boolean;
|
ativo?: boolean;
|
||||||
|
empresaId?: string; // ID da empresa vinculada (para Business Owners)
|
||||||
|
companyName?: string; // Nome da empresa vinculada
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Institution {
|
export interface Institution {
|
||||||
|
|
@ -115,4 +117,15 @@ export interface EventData {
|
||||||
institutionId?: string; // ID da instituição vinculada (obrigatório)
|
institutionId?: string; // ID da instituição vinculada (obrigatório)
|
||||||
attendees?: number; // Número de pessoas participantes
|
attendees?: number; // Número de pessoas participantes
|
||||||
courseId?: string; // ID do curso/turma relacionado ao evento
|
courseId?: string; // ID do curso/turma relacionado ao evento
|
||||||
|
fotId?: string; // ID da Turma (FOT)
|
||||||
|
typeId?: string; // ID do Tipo de Evento (UUID)
|
||||||
|
|
||||||
|
// Fields populated from backend joins (ListAgendas)
|
||||||
|
fot?: string; // Nome/Número da Turma (FOT)
|
||||||
|
curso?: string; // Nome do Curso
|
||||||
|
instituicao?: string; // Nome da Instituição
|
||||||
|
anoFormatura?: string; // Ano/Semestre
|
||||||
|
empresa?: string; // Nome da Empresa
|
||||||
|
observacoes?: string; // Observações da FOT
|
||||||
|
tipoEventoNome?: string; // Nome do Tipo de Evento
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue