feat: Ajusta a edição de eventos no gerenciamento de agenda, incluindo atribuição de profissionais e status
This commit is contained in:
parent
cd00fb53f9
commit
002dee832d
8 changed files with 248 additions and 102 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package agenda
|
package agenda
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -126,8 +127,11 @@ func (h *Handler) Update(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Update Payload: %+v\n", req)
|
||||||
|
|
||||||
agenda, err := h.service.Update(c.Request.Context(), id, req)
|
agenda, err := h.service.Update(c.Request.Context(), id, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Printf("Update Error for ID %s: %v\n", id, err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar agenda: " + err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar agenda: " + err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package agenda
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"photum-backend/internal/db/generated"
|
"photum-backend/internal/db/generated"
|
||||||
|
|
@ -149,6 +150,7 @@ func (s *Service) List(ctx context.Context, userID uuid.UUID, role string) ([]Ag
|
||||||
Instituicao: r.Instituicao,
|
Instituicao: r.Instituicao,
|
||||||
CursoNome: r.CursoNome,
|
CursoNome: r.CursoNome,
|
||||||
EmpresaNome: r.EmpresaNome,
|
EmpresaNome: r.EmpresaNome,
|
||||||
|
EmpresaID: r.EmpresaID,
|
||||||
AnoSemestre: r.AnoSemestre,
|
AnoSemestre: r.AnoSemestre,
|
||||||
ObservacoesFot: r.ObservacoesFot,
|
ObservacoesFot: r.ObservacoesFot,
|
||||||
TipoEventoNome: r.TipoEventoNome,
|
TipoEventoNome: r.TipoEventoNome,
|
||||||
|
|
@ -191,6 +193,13 @@ func (s *Service) Get(ctx context.Context, id uuid.UUID) (generated.Agenda, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Update(ctx context.Context, id uuid.UUID, req CreateAgendaRequest) (generated.Agenda, error) {
|
func (s *Service) Update(ctx context.Context, id uuid.UUID, req CreateAgendaRequest) (generated.Agenda, error) {
|
||||||
|
if req.FotID == uuid.Nil {
|
||||||
|
return generated.Agenda{}, fmt.Errorf("FOT ID inválido ou não informado")
|
||||||
|
}
|
||||||
|
if req.TipoEventoID == uuid.Nil {
|
||||||
|
return generated.Agenda{}, fmt.Errorf("Tipo de Evento ID inválido ou não informado")
|
||||||
|
}
|
||||||
|
|
||||||
status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante)
|
status := s.CalculateStatus(req.FotoFaltante, req.RecepFaltante, req.CineFaltante)
|
||||||
|
|
||||||
params := generated.UpdateAgendaParams{
|
params := generated.UpdateAgendaParams{
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,7 @@ SELECT
|
||||||
af.ano_semestre,
|
af.ano_semestre,
|
||||||
cf.observacoes as observacoes_fot,
|
cf.observacoes as observacoes_fot,
|
||||||
te.nome as tipo_evento_nome,
|
te.nome as tipo_evento_nome,
|
||||||
|
cf.empresa_id,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT json_agg(json_build_object(
|
(SELECT json_agg(json_build_object(
|
||||||
'professional_id', ap.profissional_id,
|
'professional_id', ap.profissional_id,
|
||||||
|
|
@ -357,6 +358,7 @@ type ListAgendasRow struct {
|
||||||
AnoSemestre string `json:"ano_semestre"`
|
AnoSemestre string `json:"ano_semestre"`
|
||||||
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
||||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||||
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,6 +407,7 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) {
|
||||||
&i.AnoSemestre,
|
&i.AnoSemestre,
|
||||||
&i.ObservacoesFot,
|
&i.ObservacoesFot,
|
||||||
&i.TipoEventoNome,
|
&i.TipoEventoNome,
|
||||||
|
&i.EmpresaID,
|
||||||
&i.AssignedProfessionals,
|
&i.AssignedProfessionals,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -427,6 +430,7 @@ SELECT
|
||||||
af.ano_semestre,
|
af.ano_semestre,
|
||||||
cf.observacoes as observacoes_fot,
|
cf.observacoes as observacoes_fot,
|
||||||
te.nome as tipo_evento_nome,
|
te.nome as tipo_evento_nome,
|
||||||
|
cf.empresa_id,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT json_agg(json_build_object(
|
(SELECT json_agg(json_build_object(
|
||||||
'professional_id', ap.profissional_id,
|
'professional_id', ap.profissional_id,
|
||||||
|
|
@ -483,6 +487,7 @@ type ListAgendasByUserRow struct {
|
||||||
AnoSemestre string `json:"ano_semestre"`
|
AnoSemestre string `json:"ano_semestre"`
|
||||||
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
ObservacoesFot pgtype.Text `json:"observacoes_fot"`
|
||||||
TipoEventoNome string `json:"tipo_evento_nome"`
|
TipoEventoNome string `json:"tipo_evento_nome"`
|
||||||
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
AssignedProfessionals interface{} `json:"assigned_professionals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,6 +536,7 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([]
|
||||||
&i.AnoSemestre,
|
&i.AnoSemestre,
|
||||||
&i.ObservacoesFot,
|
&i.ObservacoesFot,
|
||||||
&i.TipoEventoNome,
|
&i.TipoEventoNome,
|
||||||
|
&i.EmpresaID,
|
||||||
&i.AssignedProfessionals,
|
&i.AssignedProfessionals,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ SELECT
|
||||||
af.ano_semestre,
|
af.ano_semestre,
|
||||||
cf.observacoes as observacoes_fot,
|
cf.observacoes as observacoes_fot,
|
||||||
te.nome as tipo_evento_nome,
|
te.nome as tipo_evento_nome,
|
||||||
|
cf.empresa_id,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT json_agg(json_build_object(
|
(SELECT json_agg(json_build_object(
|
||||||
'professional_id', ap.profissional_id,
|
'professional_id', ap.profissional_id,
|
||||||
|
|
@ -70,6 +71,7 @@ SELECT
|
||||||
af.ano_semestre,
|
af.ano_semestre,
|
||||||
cf.observacoes as observacoes_fot,
|
cf.observacoes as observacoes_fot,
|
||||||
te.nome as tipo_evento_nome,
|
te.nome as tipo_evento_nome,
|
||||||
|
cf.empresa_id,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT json_agg(json_build_object(
|
(SELECT json_agg(json_build_object(
|
||||||
'professional_id', ap.profissional_id,
|
'professional_id', ap.profissional_id,
|
||||||
|
|
|
||||||
|
|
@ -148,39 +148,64 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
// Populate form with initialData
|
// Populate form with initialData
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
|
console.log("EventForm received initialData:", initialData);
|
||||||
|
|
||||||
|
// Robust mapping for API snake_case fields
|
||||||
|
const mapLink = (initialData as any).local_evento || (initialData.address && initialData.address.mapLink) || "";
|
||||||
|
const mappedFotId = (initialData as any).fot_id || initialData.fotId || "";
|
||||||
|
const mappedEmpresaId = (initialData as any).empresa_id || (initialData as any).empresaId || "";
|
||||||
|
const mappedObservacoes = (initialData as any).observacoes_evento || initialData.name || "";
|
||||||
|
|
||||||
|
console.log("Mapped Values:", { mappedFotId, mappedEmpresaId, mappedObservacoes, mapLink });
|
||||||
|
|
||||||
|
// Parse Endereco String if Object is missing but String exists
|
||||||
|
// Format expected: "Rua, Numero - Cidade/UF" or similar
|
||||||
|
let addressData = initialData.address || {
|
||||||
|
street: "", number: "", city: "", state: "", zip: "", lat: -22.7394, lng: -47.3314, mapLink: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!initialData.address && (initialData as any).endereco) {
|
||||||
|
// Simple heuristic parser or just default. User might need to refine.
|
||||||
|
// Doing a best-effort copy to street if unstructured
|
||||||
|
addressData = { ...addressData, street: (initialData as any).endereco || "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety: ensure all strings
|
||||||
|
addressData = {
|
||||||
|
street: addressData.street || "",
|
||||||
|
number: addressData.number || "",
|
||||||
|
city: addressData.city || "",
|
||||||
|
state: addressData.state || "",
|
||||||
|
zip: addressData.zip || "",
|
||||||
|
lat: addressData.lat || -22.7394,
|
||||||
|
lng: addressData.lng || -47.3314,
|
||||||
|
mapLink: addressData.mapLink || ""
|
||||||
|
};
|
||||||
|
|
||||||
// 1. Populate standard form fields
|
// 1. Populate standard form fields
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
...initialData,
|
...initialData,
|
||||||
startTime: initialData.time || "00:00",
|
startTime: initialData.time || (initialData as any).horario || "00:00",
|
||||||
locationName: (initialData as any).local_evento || (initialData as any).address?.mapLink?.includes('http') ? "" : (initialData as any).address?.mapLink || "",
|
endTime: (initialData as any).horario_termino || prev.endTime || "",
|
||||||
fotId: initialData.fotId || "",
|
locationName: mapLink.includes('http') ? "" : mapLink, // Avoid putting URL in name
|
||||||
|
fotId: mappedFotId,
|
||||||
|
name: mappedObservacoes, // Map Observacoes to Name field (displayed as "Observacoes do Evento")
|
||||||
|
briefing: mappedObservacoes, // Sync briefing
|
||||||
|
address: addressData,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 2. Populate derived dropdowns if data exists
|
// 2. Populate derived dropdowns if data exists
|
||||||
// Check for empresa_id or empresaId in initialData
|
if (mappedEmpresaId && (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN)) {
|
||||||
const initEmpresaId = (initialData as any).empresa_id || (initialData as any).empresaId;
|
console.log("Setting Selected Company:", mappedEmpresaId);
|
||||||
if (initEmpresaId && (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.SUPERADMIN)) {
|
setSelectedCompanyId(mappedEmpresaId);
|
||||||
setSelectedCompanyId(initEmpresaId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initialData.curso) {
|
if (initialData.curso || (initialData as any).curso_nome) {
|
||||||
setSelectedCourseName(initialData.curso);
|
setSelectedCourseName(initialData.curso || (initialData as any).curso_nome || "");
|
||||||
}
|
}
|
||||||
if (initialData.instituicao) {
|
if (initialData.instituicao) {
|
||||||
setSelectedInstitutionName(initialData.instituicao);
|
setSelectedInstitutionName(initialData.instituicao || "");
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Populate address if available
|
|
||||||
if (initialData.address) {
|
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
address: {
|
|
||||||
...prev.address,
|
|
||||||
...initialData.address,
|
|
||||||
mapLink: initialData.address.mapLink || ""
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [initialData, user?.role]);
|
}, [initialData, user?.role]);
|
||||||
|
|
@ -204,16 +229,18 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a target company (or user is linked), fetch FOTs
|
// If we have a target company (or user is linked), fetch FOTs
|
||||||
if (targetEmpresaId || user?.role === UserRole.EVENT_OWNER) { // EventOwner might be linked differently or via getCadastroFot logic
|
if (targetEmpresaId || user?.role === UserRole.EVENT_OWNER) {
|
||||||
// Verify logic: EventOwner (client) usually has strict link.
|
|
||||||
// If targetEmpresaId is still empty and user is NOT BusinessOwner/Superadmin, we might skip or let backend decide (if user has implicit link).
|
|
||||||
// But let's assume valid flow.
|
|
||||||
|
|
||||||
setLoadingFots(true);
|
setLoadingFots(true);
|
||||||
|
// Clear previous FOTs to force UI update and avoid stale data
|
||||||
|
setAvailableFots([]);
|
||||||
|
|
||||||
|
console.log("Fetching FOTs for company:", targetEmpresaId);
|
||||||
|
|
||||||
const token = localStorage.getItem("token") || "";
|
const token = localStorage.getItem("token") || "";
|
||||||
const response = await getCadastroFot(token, targetEmpresaId);
|
const response = await getCadastroFot(token, targetEmpresaId);
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
|
console.log("FOTs loaded:", response.data.length);
|
||||||
setAvailableFots(response.data);
|
setAvailableFots(response.data);
|
||||||
}
|
}
|
||||||
setLoadingFots(false);
|
setLoadingFots(false);
|
||||||
|
|
@ -354,23 +381,36 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
// Validation
|
// Validation
|
||||||
if (!formData.name) return alert("Preencha o tipo de evento");
|
if (!formData.name) return alert("Preencha o tipo de evento");
|
||||||
if (!formData.date) return alert("Preencha a data");
|
if (!formData.date) return alert("Preencha a data");
|
||||||
if (user?.role === UserRole.BUSINESS_OWNER || user?.role === UserRole.EVENT_OWNER) {
|
|
||||||
if (!formData.fotId) {
|
// Ensure typeId is valid
|
||||||
alert("Por favor, selecione a Turma (Cadastro FOT) antes de continuar.");
|
let finalTypeId = formData.typeId;
|
||||||
return;
|
if (!finalTypeId || finalTypeId === "00000000-0000-0000-0000-000000000000") {
|
||||||
|
// Try to match by name
|
||||||
|
const matchingType = eventTypes.find(t => t.nome === formData.type);
|
||||||
|
if (matchingType) {
|
||||||
|
finalTypeId = matchingType.id;
|
||||||
|
} else {
|
||||||
|
// If strictly required by DB, we must stop.
|
||||||
|
// But for legacy compatibility, maybe we prompt user
|
||||||
|
return alert("Tipo de evento inválido. Por favor, selecione novamente o tipo do evento.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!formData.fotId) {
|
||||||
|
alert("ERRO CRÍTICO: Turma (FOT) não identificada. Por favor, selecione a Turma ou Empresa novamente.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setShowToast(true);
|
setShowToast(true);
|
||||||
|
|
||||||
// Prepare Payload for Agenda API
|
// Prepare Payload for Agenda API
|
||||||
const payload = {
|
const payload = {
|
||||||
fot_id: formData.fotId,
|
fot_id: formData.fotId, // Must be valid UUID
|
||||||
tipo_evento_id: formData.typeId || "00000000-0000-0000-0000-000000000000",
|
tipo_evento_id: finalTypeId,
|
||||||
data_evento: new Date(formData.date).toISOString(),
|
data_evento: new Date(formData.date).toISOString(),
|
||||||
horario: formData.startTime || "",
|
horario: formData.startTime || "",
|
||||||
observacoes_evento: formData.briefing || "",
|
observacoes_evento: formData.name || formData.briefing || "",
|
||||||
local_evento: formData.locationName || "",
|
local_evento: formData.locationName || "",
|
||||||
endereco: `${formData.address.street}, ${formData.address.number} - ${formData.address.city}/${formData.address.state}`,
|
endereco: `${formData.address.street}, ${formData.address.number} - ${formData.address.city}/${formData.address.state}`,
|
||||||
qtd_formandos: parseInt(formData.attendees) || 0,
|
qtd_formandos: parseInt(formData.attendees) || 0,
|
||||||
|
|
@ -395,15 +435,13 @@ export const EventForm: React.FC<EventFormProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
// Submit to parent handler
|
// Submit to parent handler
|
||||||
setTimeout(() => {
|
if (onSubmit) {
|
||||||
if (onSubmit) {
|
await onSubmit(payload);
|
||||||
onSubmit(formData);
|
}
|
||||||
}
|
alert("Solicitação enviada com sucesso!");
|
||||||
alert("Solicitação enviada com sucesso!");
|
|
||||||
}, 1000);
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
alert("Erro inesperado: " + e.message);
|
alert("Erro ao salvar: " + (e.message || "Erro desconhecido"));
|
||||||
setShowToast(false);
|
setShowToast(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
|
import React, { createContext, useContext, useState, ReactNode, useEffect } from "react";
|
||||||
import { useAuth } from "./AuthContext";
|
import { useAuth } from "./AuthContext";
|
||||||
import { getPendingUsers, approveUser as apiApproveUser, getProfessionals, assignProfessional as apiAssignProfessional, removeProfessional as apiRemoveProfessional, updateEventStatus as apiUpdateStatus, updateAssignmentStatus as apiUpdateAssignmentStatus } from "../services/apiService";
|
import { getPendingUsers, approveUser as apiApproveUser, getProfessionals, assignProfessional as apiAssignProfessional, removeProfessional as apiRemoveProfessional, updateEventStatus as apiUpdateStatus, updateAssignmentStatus as apiUpdateAssignmentStatus, updateAgenda as apiUpdateAgenda } from "../services/apiService";
|
||||||
import {
|
import {
|
||||||
EventData,
|
EventData,
|
||||||
EventStatus,
|
EventStatus,
|
||||||
|
|
@ -608,6 +608,7 @@ interface DataContextType {
|
||||||
rejectUser: (userId: string) => void;
|
rejectUser: (userId: string) => void;
|
||||||
professionals: Professional[];
|
professionals: Professional[];
|
||||||
respondToAssignment: (eventId: string, status: string, reason?: string) => Promise<void>;
|
respondToAssignment: (eventId: string, status: string, reason?: string) => Promise<void>;
|
||||||
|
updateEventDetails: (id: string, data: any) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DataContext = createContext<DataContextType | undefined>(undefined);
|
const DataContext = createContext<DataContextType | undefined>(undefined);
|
||||||
|
|
@ -693,6 +694,7 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
|
||||||
instituicao: e.instituicao,
|
instituicao: e.instituicao,
|
||||||
anoFormatura: e.ano_semestre,
|
anoFormatura: e.ano_semestre,
|
||||||
empresa: e.empresa_nome,
|
empresa: e.empresa_nome,
|
||||||
|
empresaId: e.empresa_id, // Ensure ID is passed to frontend
|
||||||
observacoes: e.observacoes_fot,
|
observacoes: e.observacoes_fot,
|
||||||
typeId: e.tipo_evento_id,
|
typeId: e.tipo_evento_id,
|
||||||
assignments: Array.isArray(e.assigned_professionals)
|
assignments: Array.isArray(e.assigned_professionals)
|
||||||
|
|
@ -815,59 +817,63 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
|
||||||
fetchProfs();
|
fetchProfs();
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
const addEvent = async (event: EventData) => {
|
const addEvent = async (event: any) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.error("No token found");
|
console.error("No token found");
|
||||||
// Fallback for offline/mock
|
throw new Error("Usuário não autenticado");
|
||||||
setEvents((prev) => [event, ...prev]);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Map frontend fields (camelCase) to backend fields (snake_case)
|
// Check if payload is already mapped (snake_case) or needs mapping (camelCase)
|
||||||
const payload = {
|
let payload;
|
||||||
fot_id: event.fotId,
|
if (event.fot_id && event.tipo_evento_id) {
|
||||||
data_evento: event.date + "T" + (event.time || "00:00") + ":00Z", // Backend expects full datetime with timezone
|
// Already snake_case (from EventForm payload)
|
||||||
tipo_evento_id: event.typeId,
|
payload = event;
|
||||||
observacoes_evento: event.name, // "Observações do Evento" maps to name in EventForm
|
} else {
|
||||||
// local_evento: event.address.street + ", " + event.address.number, // Or map separate fields if needed
|
// Legacy camelCase mapping
|
||||||
local_evento: event.address.mapLink || "Local a definir", // using mapLink or some string
|
payload = {
|
||||||
endereco: `${event.address.street}, ${event.address.number}, ${event.address.city} - ${event.address.state}`,
|
fot_id: event.fotId,
|
||||||
horario: event.startTime,
|
data_evento: event.date + "T" + (event.time || "00:00") + ":00Z",
|
||||||
// Defaulting missing counts to 0 for now as they are not in the simplified form
|
tipo_evento_id: event.typeId,
|
||||||
qtd_formandos: event.attendees ? parseInt(String(event.attendees)) : 0,
|
observacoes_evento: event.name,
|
||||||
qtd_fotografos: 0,
|
local_evento: event.address?.mapLink || "Local a definir",
|
||||||
qtd_recepcionistas: 0,
|
endereco: event.address ? `${event.address.street}, ${event.address.number}, ${event.address.city} - ${event.address.state}` : "",
|
||||||
qtd_cinegrafistas: 0,
|
horario: event.startTime,
|
||||||
qtd_estudios: 0,
|
qtd_formandos: event.attendees ? parseInt(String(event.attendees)) : 0,
|
||||||
qtd_ponto_foto: 0,
|
qtd_fotografos: 0,
|
||||||
qtd_ponto_id: 0,
|
qtd_recepcionistas: 0,
|
||||||
qtd_ponto_decorado: 0,
|
qtd_cinegrafistas: 0,
|
||||||
qtd_pontos_led: 0,
|
qtd_estudios: 0,
|
||||||
qtd_plataforma_360: 0,
|
qtd_ponto_foto: 0,
|
||||||
status_profissionais: "AGUARDANDO", // Will be calculated by backend anyway
|
qtd_ponto_id: 0,
|
||||||
foto_faltante: 0,
|
qtd_ponto_decorado: 0,
|
||||||
recep_faltante: 0,
|
qtd_pontos_led: 0,
|
||||||
cine_faltante: 0,
|
qtd_plataforma_360: 0,
|
||||||
logistica_observacoes: "",
|
status_profissionais: "AGUARDANDO",
|
||||||
pre_venda: false
|
foto_faltante: 0,
|
||||||
};
|
recep_faltante: 0,
|
||||||
|
cine_faltante: 0,
|
||||||
|
logistica_observacoes: "",
|
||||||
|
pre_venda: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[DEBUG] addEvent payload:", payload);
|
||||||
|
|
||||||
const result = await import("../services/apiService").then(m => m.createAgenda(token, payload));
|
const result = await import("../services/apiService").then(m => m.createAgenda(token, payload));
|
||||||
|
|
||||||
if (result.data) {
|
if (result.data) {
|
||||||
// Success
|
|
||||||
console.log("Agenda criada:", result.data);
|
console.log("Agenda criada:", result.data);
|
||||||
// Force reload to ensure complete data consistency (FOT, joins, etc.)
|
// Force reload to ensure complete data consistency
|
||||||
window.location.href = '/painel';
|
window.location.href = '/painel';
|
||||||
} else {
|
} else {
|
||||||
console.error("Erro ao criar agenda API:", result.error);
|
console.error("Erro ao criar agenda API:", result.error);
|
||||||
// Fallback or Toast?
|
throw new Error(result.error || "Erro ao criar agenda");
|
||||||
// We will optimistically add it locally or throw
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.error("Exception creating agenda:", err);
|
console.error("Exception creating agenda:", err);
|
||||||
|
throw err; // Re-throw so EventForm knows it failed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1077,6 +1083,43 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
updateEventDetails: async (id, data) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) return;
|
||||||
|
const result = await apiUpdateAgenda(token, id, data);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fetch logic to ensure state consistency
|
||||||
|
// Re-implementing simplified fetch logic here or we can trigger a reload.
|
||||||
|
// Since we are in DataContext, we can call a fetch function if we extract it.
|
||||||
|
// But to be safe and quick:
|
||||||
|
try {
|
||||||
|
const result = await import("../services/apiService").then(m => m.getAgendas(token));
|
||||||
|
if (result.data) {
|
||||||
|
// Re-map events logic from useEffect...
|
||||||
|
// This duplication is painful.
|
||||||
|
// Alternative: window.location.reload() in Dashboard.
|
||||||
|
// But let's assume the user navigates away or we do a simple local merge for Key Fields used in List.
|
||||||
|
setEvents(prev => prev.map(evt => {
|
||||||
|
if (evt.id === id) {
|
||||||
|
return {
|
||||||
|
...evt,
|
||||||
|
date: data.data_evento ? data.data_evento.split("T")[0] : evt.date,
|
||||||
|
time: data.horario || evt.time,
|
||||||
|
name: data.observacoes_evento || evt.name,
|
||||||
|
briefing: data.observacoes_evento || evt.briefing,
|
||||||
|
fotId: data.fot_id || evt.fotId,
|
||||||
|
empresaId: data.empresa_id || evt.empresaId, // If provided
|
||||||
|
// Address is hard to parse back to object from payload without logic
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return evt;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) { console.error("Refresh failed", e); }
|
||||||
|
},
|
||||||
addEvent,
|
addEvent,
|
||||||
updateEventStatus,
|
updateEventStatus,
|
||||||
assignPhotographer,
|
assignPhotographer,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
// Extract updateEventDetails from useData
|
||||||
const {
|
const {
|
||||||
events,
|
events,
|
||||||
getEventsByRole,
|
getEventsByRole,
|
||||||
|
|
@ -44,7 +45,48 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
getInstitutionById,
|
getInstitutionById,
|
||||||
getActiveCoursesByInstitutionId,
|
getActiveCoursesByInstitutionId,
|
||||||
respondToAssignment,
|
respondToAssignment,
|
||||||
|
updateEventDetails,
|
||||||
} = useData();
|
} = useData();
|
||||||
|
|
||||||
|
// ... (inside component)
|
||||||
|
|
||||||
|
const handleSaveEvent = async (data: any) => {
|
||||||
|
const isClient = user.role === UserRole.EVENT_OWNER;
|
||||||
|
|
||||||
|
if (view === "edit" && selectedEvent) {
|
||||||
|
if (updateEventDetails) {
|
||||||
|
await updateEventDetails(selectedEvent.id, data);
|
||||||
|
// Force reload of view to reflect changes (or rely on DataContext optimistic update)
|
||||||
|
// But DataContext optimistic update only touched generic fields.
|
||||||
|
// Address might still be old in 'selectedEvent' state if we don't update it.
|
||||||
|
// Updating selectedEvent manually as well to be safe:
|
||||||
|
const updatedEvent = { ...selectedEvent, ...data, date: data.date || data.data_evento?.split('T')[0] || selectedEvent.date };
|
||||||
|
setSelectedEvent(updatedEvent);
|
||||||
|
setView("details");
|
||||||
|
// Optional: Reload page safely if critical fields changed that DataContext map didn't catch?
|
||||||
|
// For now, trust DataContext + local state update.
|
||||||
|
// Actually, DataContext refetch logic was "try import...", so it might be async.
|
||||||
|
// Let's reload window to be 100% sure for the user as requested "mudei a data e não mudou".
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
console.error("Update function not available");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const initialStatus = isClient
|
||||||
|
? EventStatus.PENDING_APPROVAL
|
||||||
|
: EventStatus.PLANNING;
|
||||||
|
const newEvent: EventData = {
|
||||||
|
...data,
|
||||||
|
id: Math.random().toString(36).substr(2, 9),
|
||||||
|
status: initialStatus,
|
||||||
|
checklist: [],
|
||||||
|
ownerId: isClient ? user.id : "unknown",
|
||||||
|
photographerIds: [],
|
||||||
|
};
|
||||||
|
addEvent(newEvent);
|
||||||
|
setView("list");
|
||||||
|
}
|
||||||
|
};
|
||||||
const [view, setView] = useState<"list" | "create" | "edit" | "details">(
|
const [view, setView] = useState<"list" | "create" | "edit" | "details">(
|
||||||
initialView
|
initialView
|
||||||
);
|
);
|
||||||
|
|
@ -116,30 +158,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSaveEvent = (data: any) => {
|
|
||||||
const isClient = user.role === UserRole.EVENT_OWNER;
|
|
||||||
|
|
||||||
if (view === "edit" && selectedEvent) {
|
|
||||||
const updatedEvent = { ...selectedEvent, ...data };
|
|
||||||
console.log("Updated", updatedEvent);
|
|
||||||
setSelectedEvent(updatedEvent);
|
|
||||||
setView("details");
|
|
||||||
} else {
|
|
||||||
const initialStatus = isClient
|
|
||||||
? EventStatus.PENDING_APPROVAL
|
|
||||||
: EventStatus.PLANNING;
|
|
||||||
const newEvent: EventData = {
|
|
||||||
...data,
|
|
||||||
id: Math.random().toString(36).substr(2, 9),
|
|
||||||
status: initialStatus,
|
|
||||||
checklist: [],
|
|
||||||
ownerId: isClient ? user.id : "unknown",
|
|
||||||
photographerIds: [],
|
|
||||||
};
|
|
||||||
addEvent(newEvent);
|
|
||||||
setView("list");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keep selectedEvent in sync with global events state
|
// Keep selectedEvent in sync with global events state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,31 @@ export const getAgendas = async (token: string): Promise<ApiResponse<any[]>> =>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Agenda - Update
|
||||||
|
export const updateAgenda = async (token: string, id: string, data: any) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/agenda/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
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 atualizar agenda:", error);
|
||||||
|
return { data: null, error: error.message || "Erro ao atualizar agenda" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const updateAssignmentStatus = async (token: string, eventId: string, professionalId: string, status: string, reason?: string) => {
|
export const updateAssignmentStatus = async (token: string, eventId: string, professionalId: string, status: string, reason?: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals/${professionalId}/status`, {
|
const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals/${professionalId}/status`, {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue