From 28b76a0f5449053eb4bb78105c2a091c1b212c46 Mon Sep 17 00:00:00 2001 From: NANDO9322 Date: Tue, 16 Dec 2025 18:10:46 -0300 Subject: [PATCH] =?UTF-8?q?feat(fot-management):=20implementa=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20a=C3=A7=C3=B5es=20editar/excluir=20e=20corre=C3=A7?= =?UTF-8?q?=C3=A3o=20no=20mapeamento=20da=20agenda?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implementadas ações de Editar e Excluir na página de Gestão de FOT - Adicionado filtro de busca para FOTs - Corrigido desalinhamento de colunas na tabela de Gestão de FOT - Atualizado FotForm para suportar a edição de registros existentes - Corrigido erro de renderização do React no Dashboard mapeando corretamente os objetos de atribuição - Removidos dados de mock (INITIAL_EVENTS) e corrigido erro de referência nula no DataContext - Adicionados métodos de atualização/exclusão ao apiService --- backend/Makefile | 3 + backend/cmd/api/main.go | 5 + backend/cmd/importer/main.go | 231 +++++++++ backend/docs/docs.go | 46 ++ backend/docs/swagger.json | 46 ++ backend/docs/swagger.yaml | 31 ++ backend/importacao_fots.csv | 483 ++++++++++++++++++ backend/internal/agenda/handler.go | 147 ++++++ backend/internal/agenda/service.go | 166 ++++-- backend/internal/db/generated/agenda.sql.go | 384 +++++++++++--- backend/internal/db/generated/models.go | 10 + .../db/generated/profissionais.sql.go | 5 +- backend/internal/db/queries/agenda.sql | 53 +- backend/internal/db/queries/profissionais.sql | 3 +- backend/internal/db/schema.sql | 13 +- backend/internal/profissionais/handler.go | 2 + frontend/components/EventTable.tsx | 184 ++++--- frontend/components/FotForm.tsx | 43 +- frontend/contexts/DataContext.tsx | 137 ++++- frontend/pages/CourseManagement.tsx | 130 ++++- frontend/pages/Dashboard.tsx | 228 +++------ frontend/services/apiService.ts | 223 ++++++++ frontend/types.ts | 25 + 23 files changed, 2192 insertions(+), 406 deletions(-) create mode 100644 backend/cmd/importer/main.go create mode 100644 backend/importacao_fots.csv diff --git a/backend/Makefile b/backend/Makefile index d347cad..9a827f7 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -33,3 +33,6 @@ test: ## Executa os testes swagger: ## Gera documentação Swagger swag init -g cmd/api/main.go -o docs + +import-fot: ## Importa dados de FOT do CSV (Use DB_DSN=... make import-fot para produção) + go run cmd/importer/main.go diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index d1d5a59..6e5b6b8 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -190,6 +190,11 @@ func main() { api.GET("/agenda/:id", agendaHandler.Get) api.PUT("/agenda/:id", agendaHandler.Update) api.DELETE("/agenda/:id", agendaHandler.Delete) + api.POST("/agenda/:id/professionals", agendaHandler.AssignProfessional) + api.DELETE("/agenda/:id/professionals/:profId", agendaHandler.RemoveProfessional) + api.GET("/agenda/:id/professionals", agendaHandler.GetProfessionals) + api.PATCH("/agenda/:id/professionals/:profId/status", agendaHandler.UpdateAssignmentStatus) + api.PATCH("/agenda/:id/status", agendaHandler.UpdateStatus) admin := api.Group("/admin") { diff --git a/backend/cmd/importer/main.go b/backend/cmd/importer/main.go new file mode 100644 index 0000000..f092145 --- /dev/null +++ b/backend/cmd/importer/main.go @@ -0,0 +1,231 @@ +package main + +import ( + "context" + "database/sql" + "encoding/csv" + "fmt" + "io" + "log" + "os" + "strconv" + "strings" + + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/joho/godotenv" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/transform" +) + +func main() { + // Load .env + // Try loading from current directory first, typical when running 'go run cmd/importer/main.go' from root + if err := godotenv.Load(".env"); err != nil { + // Fallback for when running from cmd/importer + if err := godotenv.Load("../../.env"); err != nil { + log.Println("Warning: .env file not found in .env or ../../.env") + } + } + + dbURL := os.Getenv("DB_DSN") + if dbURL == "" { + log.Fatal("DB_DSN is not set") + } + + db, err := sql.Open("pgx", dbURL) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + defer db.Close() + + if err := db.Ping(); err != nil { + log.Fatalf("Failed to ping database: %v", err) + } + + // Open CSV File + filename := "importacao_fots.csv" + f, err := os.Open(filename) + if err != nil { + log.Fatalf("Failed to open CSV file: %v", err) + } + defer f.Close() + + // Use Windows-1252 Decoder + r := csv.NewReader(transform.NewReader(f, charmap.Windows1252.NewDecoder())) + r.Comma = ';' + r.LazyQuotes = true // Allow messy quotes + + // Read Header + header, err := r.Read() + if err != nil { + log.Fatalf("Failed to read header: %v", err) + } + fmt.Printf("Header: %v\n", header) + + ctx := context.Background() + rowsProcessed := 0 + rowsInserted := 0 + rowsFailed := 0 + + for { + record, err := r.Read() + if err == io.EOF { + break + } + if err != nil { + log.Printf("Error reading row %d: %v. Skipping.", rowsProcessed+1, err) + rowsFailed++ + continue + } + rowsProcessed++ + + // Map Columns (Indices based on "FOT;Empresa;EF I / EF II;Observa‡äes;Institui‡Æo;Ano Formatura;Cidade;Estado;Gastos Capta‡Æo;Pr‚ Venda") + // 0: FOT + // 1: Empresa + // 2: Cursos (was EF I / EF II) + // 3: Observacoes + // 4: Instituicao + // 5: Ano Formatura + // 6: Cidade + // 7: Estado + // 8: Gastos Captacao + // 9: Pre Venda + + fotStr := strings.TrimSpace(record[0]) + empresaName := strings.TrimSpace(record[1]) + cursoName := strings.TrimSpace(record[2]) + observacoes := strings.TrimSpace(record[3]) + instituicao := strings.TrimSpace(record[4]) + anoFormatura := strings.TrimSpace(record[5]) + cidade := strings.TrimSpace(record[6]) + estado := strings.TrimSpace(record[7]) + gastosStr := strings.TrimSpace(record[8]) + preVendaStr := strings.TrimSpace(record[9]) + + // Basic Validation + if fotStr == "" || fotStr == "FOT" { // Skip header repetition or empty lines + continue + } + + fot, err := strconv.Atoi(fotStr) + if err != nil { + log.Printf("Row %d: Invalid FOT '%s'. Skipping.", rowsProcessed, fotStr) + rowsFailed++ + continue + } + + // 1. Upsert Empresa + var empresaID string + if empresaName == "" { + empresaName = "Não Identificada" + } + err = db.QueryRowContext(ctx, ` + INSERT INTO empresas (nome) VALUES ($1) + ON CONFLICT (nome) DO UPDATE SET nome = EXCLUDED.nome + RETURNING id`, empresaName).Scan(&empresaID) + if err != nil { + log.Printf("Row %d: Failed to upsert empresa '%s': %v", rowsProcessed, empresaName, err) + rowsFailed++ + continue + } + + // 2. Upsert Curso + var cursoID string + if cursoName == "" || cursoName == "-" { + cursoName = "Geral" + } + err = db.QueryRowContext(ctx, ` + INSERT INTO cursos (nome) VALUES ($1) + ON CONFLICT (nome) DO UPDATE SET nome = EXCLUDED.nome + RETURNING id`, cursoName).Scan(&cursoID) + if err != nil { + log.Printf("Row %d: Failed to upsert curso '%s': %v", rowsProcessed, cursoName, err) + rowsFailed++ + continue + } + + // 3. Upsert Ano Formatura + var anoID string + if anoFormatura == "" { + anoFormatura = "Indefinido" + } + err = db.QueryRowContext(ctx, ` + INSERT INTO anos_formaturas (ano_semestre) VALUES ($1) + ON CONFLICT (ano_semestre) DO UPDATE SET ano_semestre = EXCLUDED.ano_semestre + RETURNING id`, anoFormatura).Scan(&anoID) + if err != nil { + log.Printf("Row %d: Failed to upsert ano '%s': %v", rowsProcessed, anoFormatura, err) + rowsFailed++ + continue + } + + // 4. Parse Currency (Gastos Captação) + // Format: "R$ 2.176,60" -> 2176.60 + gastosVal := 0.0 + if gastosStr != "" && gastosStr != "-" { + // Remove "R$", "." and trim + clean := strings.ReplaceAll(gastosStr, "R$", "") + clean = strings.ReplaceAll(clean, ".", "") // Remove thousand separator + clean = strings.ReplaceAll(clean, ",", ".") // Replace decimal separator + clean = strings.TrimSpace(clean) + + // Handle trailing/leading spaces hidden chars if any + // Just parse float + val, err := strconv.ParseFloat(clean, 64) + if err == nil { + gastosVal = val + } else { + // log.Printf("Row %d: Warning parsing gastos '%s' -> '%s': %v", rowsProcessed, gastosStr, clean, err) + } + } + + // 5. Parse Boolean (Pre Venda) + preVenda := false + if strings.ToLower(preVendaStr) == "sim" { + preVenda = true + } + + // 6. Insert Cadastro FOT + _, err = db.ExecContext(ctx, ` + INSERT INTO cadastro_fot ( + fot, empresa_id, curso_id, ano_formatura_id, + instituicao, cidade, estado, observacoes, + gastos_captacao, pre_venda + ) VALUES ( + $1, $2, $3, $4, + $5, $6, $7, $8, + $9, $10 + ) + ON CONFLICT (fot) DO UPDATE SET + empresa_id = EXCLUDED.empresa_id, + curso_id = EXCLUDED.curso_id, + ano_formatura_id = EXCLUDED.ano_formatura_id, + instituicao = EXCLUDED.instituicao, + cidade = EXCLUDED.cidade, + estado = EXCLUDED.estado, + observacoes = EXCLUDED.observacoes, + gastos_captacao = EXCLUDED.gastos_captacao, + pre_venda = EXCLUDED.pre_venda, + updated_at = NOW() + `, + fot, empresaID, cursoID, anoID, + instituicao, cidade, estado, observacoes, + gastosVal, preVenda, + ) + + if err != nil { + log.Printf("Row %d: Failed to insert FOT %d: %v", rowsProcessed, fot, err) + rowsFailed++ + } else { + rowsInserted++ + if rowsInserted%50 == 0 { + fmt.Printf("Progress: %d records upserted...\n", rowsInserted) + } + } + } + + fmt.Printf("\n--- Import Summary ---\n") + fmt.Printf("Total Rows Processed: %d\n", rowsProcessed) + fmt.Printf("Successfully Upserted: %d\n", rowsInserted) + fmt.Printf("Failed: %d\n", rowsFailed) +} diff --git a/backend/docs/docs.go b/backend/docs/docs.go index efa92bf..917ae60 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -651,6 +651,49 @@ const docTemplate = `{ } } }, + "/api/agenda/{id}/professionals": { + "get": { + "tags": [ + "agenda" + ], + "summary": "Get professionals assigned to agenda", + "responses": {} + }, + "post": { + "tags": [ + "agenda" + ], + "summary": "Assign professional to agenda", + "responses": {} + } + }, + "/api/agenda/{id}/professionals/{profId}": { + "delete": { + "tags": [ + "agenda" + ], + "summary": "Remove professional from agenda", + "responses": {} + } + }, + "/api/agenda/{id}/professionals/{profId}/status": { + "patch": { + "tags": [ + "agenda" + ], + "summary": "Update professional assignment status", + "responses": {} + } + }, + "/api/agenda/{id}/status": { + "patch": { + "tags": [ + "agenda" + ], + "summary": "Update agenda status", + "responses": {} + } + }, "/api/anos-formaturas": { "get": { "security": [ @@ -2867,6 +2910,9 @@ const docTemplate = `{ "educacao_simpatia": { "type": "integer" }, + "email": { + "type": "string" + }, "endereco": { "type": "string" }, diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 38ff923..1c26d25 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -645,6 +645,49 @@ } } }, + "/api/agenda/{id}/professionals": { + "get": { + "tags": [ + "agenda" + ], + "summary": "Get professionals assigned to agenda", + "responses": {} + }, + "post": { + "tags": [ + "agenda" + ], + "summary": "Assign professional to agenda", + "responses": {} + } + }, + "/api/agenda/{id}/professionals/{profId}": { + "delete": { + "tags": [ + "agenda" + ], + "summary": "Remove professional from agenda", + "responses": {} + } + }, + "/api/agenda/{id}/professionals/{profId}/status": { + "patch": { + "tags": [ + "agenda" + ], + "summary": "Update professional assignment status", + "responses": {} + } + }, + "/api/agenda/{id}/status": { + "patch": { + "tags": [ + "agenda" + ], + "summary": "Update agenda status", + "responses": {} + } + }, "/api/anos-formaturas": { "get": { "security": [ @@ -2861,6 +2904,9 @@ "educacao_simpatia": { "type": "integer" }, + "email": { + "type": "string" + }, "endereco": { "type": "string" }, diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 7e4b3f0..f759bb4 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -301,6 +301,8 @@ definitions: type: integer educacao_simpatia: type: integer + email: + type: string endereco: type: string equipamentos: @@ -862,6 +864,35 @@ paths: summary: Update agenda event tags: - agenda + /api/agenda/{id}/professionals: + get: + responses: {} + summary: Get professionals assigned to agenda + tags: + - agenda + post: + responses: {} + summary: Assign professional to agenda + tags: + - agenda + /api/agenda/{id}/professionals/{profId}: + delete: + responses: {} + summary: Remove professional from agenda + tags: + - agenda + /api/agenda/{id}/professionals/{profId}/status: + patch: + responses: {} + summary: Update professional assignment status + tags: + - agenda + /api/agenda/{id}/status: + patch: + responses: {} + summary: Update agenda status + tags: + - agenda /api/anos-formaturas: get: consumes: diff --git a/backend/importacao_fots.csv b/backend/importacao_fots.csv new file mode 100644 index 0000000..0abc7b2 --- /dev/null +++ b/backend/importacao_fots.csv @@ -0,0 +1,483 @@ +FOT;Empresa;EF I / EF II;Observaes;Instituio;Ano Formatura;Cidade;Estado;Gastos Captao;Pr Venda +23112;Viva SP;Enfermagem; CTT - 6132 - Anhang Sorocaba / med. Vet CTT - 6916- Puc sorocaba / enferm;PUC Sorocaba;2023.2;Sorocaba;SP; R$ 2.176,60 ; +23121;Perfil;Med. Veterinria;-;Unifaj;2023.2;Jaguariuna;SP; R$ 7.649,00 ;Sim +23144;Smart;Unificados;Adm/Direito/Med Vet;Unimep;2023.2;Piracicaba;SP; R$ 3.087,00 ; +23145;Smart;Unificados;Eng Civil / Eng Produo / Nutio ;Unip/Anhanguera/ Anhembi;2023.2;Piracicaba;SP; R$ 1.592,00 ; +23252;Viva SP;Unificados;Juno UNESP;UNESP;2023.2;Rio Claro;SP; R$ 3.950,00 ; +23266;Viva SP;Unificados;CTT 6229 / 6213 Psico / Biomed;UNIP;2023.2;So Paulo;SP; R$ 6.835,00 ; +23270;Ponta Eventos;Superor Diversos;-;FATEC Piracicaba;2023.2;Piracicaba;SP; R$ 3.060,00 ; +23282;Ponta Eventos;Superor Diversos;-;FATEC GARA;2023.2;;SP; R$ 7.500,00 ; +23286;Perfil;Superor Diversos;CTT - 733;Anhanguera Americana;2023.2;Americana;SP; R$ 2.681,80 ; +23288;Golden;Superor Diversos;-;Unopar Anhaguera;2023.2;So Bernardo do Campo;SP; R$ 10.930,00 ; +23289;Prime;Superor Diversos;-;FATEC Jundia;2023.2;Jundia;SP; R$ 5.787,60 ; +23303;Prime;Superor Diversos;-;FATEC Taquaritinga;2023.2;Taquaritnga;SP; R$ 7.566,00 ; +23304;Perfil;Superor Diversos;CTT 740;Anhanguera Nova Odessa;2023.2;Nova Odessa;SP; R$ 2.339,00 ; +24001;Viva SP;Medicina;Turma Rebeca Oliveira - 5877;USJT;2024.2;Cubato;SP; R$ 41.360,22 ;Sim +24002;Smart;Odontologia;-;Unicamp;2024.2;Campinas;SP; R$ 9.648,68 ;Sim +24003;Arte Formaturas;Unificados;Odonto / medicina veterinaria;UNIFAI;2024.2;Adamantina;SP; R$ 9.979,86 ; +24005;Smart;EFII;-;Objetivo Piracicaba;2024.2;Piracicaba;SP; R$ 10.684,60 ; +24006;Smart;EM;-;Antares;2024.2;Americana;SP; R$ 6.541,40 ; +24007;Smart;EFII;-;Antares;2024.2;Americana;SP; R$ 10.727,80 ; +24008;Perfil;EFII;CTT 719;Colgio Americana;2024.2;Americana;SP; R$ 4.258,90 ; +24009;Viva SP;EM;-;Porto Seguro - Valinhos;2024.2;Valinhos;SP; R$ 19.312,20 ; +24010;Viva SP;Farmcia;CTT 8112 - Debora Campos;USP;2024.2;;SP;#VALOR!; +24011;Smart;EM;-;Anglo Portal;2024.2;Piracicaba;SP; R$ 8.135,60 ; +24012;Smart;EF II / EM;-;COC Piracicaba;2024.2;Piracicaba;SP; R$ 7.211,10 ; +24013;Smart;EFII;-;Liceu Terras;2024.2;Piracicaba;SP; R$ 6.124,20 ; +24014;Smart;EFII;-;KOELLI;2024.2;Piracicaba;SP; R$ 19.979,83 ;Sim +24015;Smart;EM;-;KOELLI;2024.2;Piracicaba;SP; R$ 20.559,40 ;Sim +24016;Smart;EM;-;Anglo Araras;2024.2;Araras;SP; R$ 7.558,75 ; +24017;Smart;EM;-;INSA Araras;2024.2;Araras;SP; R$ 6.586,17 ; +24018;Smart;EFII;-;Anglo Cidade Alta;2024.2;Piracicaba;SP; R$ 4.735,00 ; +24019;Smart;EFII;-;Dom Bosco Assuno;2024.2;Piracicaba;SP; R$ 7.021,60 ; +24020;Smart;EM;-;Anglo Portal Limeira;2024.2;Limeira;SP; R$ 6.257,40 ; +24021;Smart;EM;-;Anglo Cidade Alta;2024.2;Piracicaba;SP; R$ 7.255,70 ; +24022;Smart;EF II / EM;-;Colgio Lumisol;2024.2;Piracicaba;SP; R$ 6.979,50 ; +24023;Smart;Superor Diversos;-;EEP;2024.2;Piracicaba;SP; R$ 9.808,50 ; +24024;Perfil;EF I / EF II / EM;-;Oficina do Estudante;2024.2;Campinas;SP; R$ 32.654,60 ; +24025;Perfil;EF I / EF II / EM;CTT - 736;Colgio Eduq;2024.2;Rio Claro;SP; R$ 16.687,00 ; +24026;Perfil;EF II / EM;CTT - 741;Colgio Educativa;2024.2;Sumar;SP; R$ 15.823,50 ; +24027;Perfil;EM / TEC;CTT - 675;ETEC Polivalente;2024.2;Americana;SP; R$ 20.235,50 ; +24028;Perfil;EF II / EM;CTT - 727;Objetivo S.B.O;2024.2;Santa Barbara d' Oeste;SP; R$ 4.751,00 ; +24029;Perfil;EM;CTT - 729;Poliedro;2024.2;Campinas;SP; R$ 5.735,60 ; +24030;Perfil;EM;CTT - 735;Imaculada Mogi Mirim;2024.2;Mogi Mirim;SP; R$ 5.197,00 ; +24031;Perfil;EM;CTT - 717;ETAPA Valinhos;2024.2;Valinhos;SP; R$ 5.947,90 ; +24032;Viva SP;EM;-;Poliedro Colao;2024.2;Campinas;SP; R$ 12.126,30 ; +24033;Perfil;EF I / EF II / EM;CTT - 734;Colgio IESC - Nova Odessa;2024.2;Nova Odessa;SP; R$ 3.717,05 ; +24034;Perfil;EF II / EM;CTT - 743;COLGIO BOM JESUS ITATIBA;2024.2;Itatiba;SP; R$ 5.179,20 ; +24035;Perfil;EF I / EF II / EM;CTT - 737;COLGIO METROPOLITAN PAULINENSE ;2024.2;Paulinia;SP; R$ 15.369,30 ; +24038;Perfil;EF II / EM;CTT - 744;SESI - S.B.O;2024.2;Santa Barbara d' Oeste;SP; R$ 8.940,00 ; +24039;Perfil;EFII;CTT - 738;Imaculada Campinas;2024.2;Campinas;SP; R$ 11.679,20 ; +24040;Forcamp;EM;Baile junto o Taquaral fot 24041;Progresso Cambu;2024.2;Campinas;SP; R$ 10.349,80 ; +24041;Forcamp;EM;Baile junto o Cambu fot 24040;Progresso Taquaral;2024.2;Campinas;SP; R$ 4.814,50 ; +24042;Viva SP;EM;CTT - 8486;Imaculada Campinas;2024.2;Campinas;SP; R$ 8.741,90 ; +24100;Arte Formaturas;Unificados;Enfermagem/Fisioterapia;AEMS;2024.2;Trs Lagoas;MS; R$ 5.001,74 ; +24102;Viva SP;Odontologia;CTT - 8305;UNIP;2024.2;Campinas;SP; R$ 2.773,80 ;Sim +24200;Arte Formaturas;Direito;-;UNIFAI;2024.2;Adamantina;SP; R$ 3.942,92 ; +24201;Arte Formaturas;Unificados;Agronomia/Enfermagem/Farmacia/Med Vet;AEMS;2024.2;Trs Lagoas;MS; R$ - ; +24202;Viva SP;Superor Diversos; Juno CTT-7579 USCS-Odonto/UNICSUl-Psico/EISNTIN-Enfer/FASM-Enfer-Fisio;USCS-UNICSUL-EISTEIN-FASM;2024.2;So Caetano do Sul;SP; R$ 15.653,20 ; +24205;Arte Formaturas;Biomedicina;-;UNIFAI;2024.2;Adamantina;SP; R$ 3.011,09 ; +25001;Viva SP;Unificados;Unificados Comunicao;PUC Campinas;2025.2;Campinas;SP; R$ - ; +25005;Smart;Enfermagem;-;UNIARARAS;2025.2;Araras;SP; R$ 1.576,80 ; +25100;Viva SP;Medicina;T 3;Unaerp;2025.2;Guaruj;SP; R$ 5.578,90 ;Sim +25101;Arte Formaturas;Pedagogia;-;UFMS;2025.2;Dracena;SP; R$ - ; +25112;Arte Formaturas;Unificados;Enfer / Fisio / Biomed;FADAP;2025.2;Tup;SP; R$ - ; +25102;Arte Formaturas;Unificados;CTT 1342 - Biomed/Enfermage/Farmacia/Odonto;AEMS;2025.2;Trs Lagoas;MS; R$ 8.976,70 ; +25103;Arte Formaturas;Med. Veterinria;-;AEMS;2025.2;Trs Lagoas;MS; R$ 3.552,69 ; +25105;Arte Formaturas;Pedagogia;-;UFMS;2025.2;Trs Lagoas;MS; R$ - ; +25106;Arte Formaturas;Unificados;CTT - 1353 - Direito / Med. Vet;UNIFAI;2025.2;Adamantina;SP; R$ 4.914,40 ; +25108;Arte Formaturas;Odontologia;-;UNIFAI;2025.2;Adamantina;SP; R$ 482,92 ; +25109;Arte Formaturas;Unificados;Agronomia/Farmcia/Fisioterapia / Biomed;UNIFAI;2025.2;Adamantina;SP;#REF!; +25110;Arte Formaturas;Biomedicina;-;UNIFAI;2025.2;Adamantina;SP; R$ 1.215,60 ; +25201;Viva SP;Enfermagem;-;Albert Einstein;2025.2;So Paulo;SP; R$ - ; +25500;Viva SP;Odontologia;Turma Hellen;UNISA;2025.2;So Paulo;SP; R$ 1.130,60 ; +25501;Arte Formaturas;Unificados;Biomedicina + Enfermagem + Fisioterapia ;FADAP;2025.2;Tup;SP; R$ - ; +26100;Arte Formaturas;Odontologia;-;UNIFAI;2026.2;Adamantina;SP; R$ 3.008,16 ; +26403;Viva SP;Medicina;T4;Unoeste;2026.2;Guaruj;SP; R$ 440,00 ;sim +27005;Viva SP;Medicina;T4 - So Judas Cubato CTT - 8126 Vinicius;USJT;2027.2;Cubato;SP; R$ 16.920,00 ;Sim +27100;Viva SP;Medicina;T6 ;Unaerp;2027.1;Guaruj;SP; R$ 5.832,95 ; +28001;Arte Formaturas;Medicina;-;Unifadra;2028.2;Dracena;SP; R$ - ; +26015;Viva SP;Odontologia;CTT - 8320;FHO;2026.2;Araras;SP; R$ 4.609,40 ;Sim +22368;Viva SP;Direito;CTT - 6994 (Turma se forma em 2024);Mackenzie;2022.2;Campinas;SP; R$ 15.866,00 ; +24045;Perfil;Med. Veterinria;CTT - 694;UNIFAJ;2024.2;Jaguariuna;SP; R$ 14.492,97 ;Sim +24046;Perfil;EF II / EM;CTT - 746;Colgio Dom Pedro;2024.2;Americana;SP; R$ 17.081,50 ; +24047;Forcamp;EF I / EF II / EM;T 42;Jardins Mediterrneo;2024.2;Campinas;SP; R$ 7.963,20 ; +24048;Forcamp;EM;-;Objetivo Baro Geraldo;2024.2;Campinas;SP; R$ 9.415,02 ; +24049;Forcamp;EM;-;Objetivo Cambu;2024.2;Campinas;SP; R$ 6.166,70 ; +24051;Forcamp;EF II / EM;-;Progresso Vinhedo;2024.2;Vinhedo;SP; R$ 8.289,80 ; +24052;Forcamp;EFII;-;Progresso Cambu;2024.2;Campinas;SP; R$ 3.297,70 ; +24053;Forcamp;EFII;-;Progresso Taquaral;2024.2;Campinas;SP; R$ 3.048,40 ; +24054;Forcamp;EF II / EM;-;Anglo Salto;2024.2;Salto;SP; R$ 8.847,30 ; +24055;Forcamp;EFII;-;Escala;2024.2;Indaiatuba;SP; R$ 4.219,90 ; +24056;Forcamp;EF II / EM;-;Colgio Conquista;2024.2;Indaiatuba;SP; R$ 13.264,00 ; +24058;Forcamp;EF II / EM;-;Colgio Rodin;2024.2;Indaiatuba;SP; R$ 15.888,10 ; +24060;Forcamp;EF I / EF II / EM;-;Progresso Itu;2024.2;Itu;SP; R$ 11.925,30 ; +24061;Forcamp;EM;-;Colgio Elite;2024.2;Indaiatuba;SP; R$ 3.271,70 ; +24062;Forcamp;EFII;-;Objetivo Baro Geraldo;2024.2;Campinas;SP; R$ 3.329,50 ; +24063;Forcamp;EF II / EM;-;Progresso Indaiatuba;2024.2;Indaiatuba;SP; R$ 8.908,10 ; +24064;Forcamp;EFII;-;Objetivo Cambu;2024.2;Campinas;SP; R$ 2.707,30 ; +24066;Forcamp;EF II / EM;-;Colgio Divino;2024.2;Itu;SP; R$ 13.938,80 ; +24067;Forcamp;EF I / EF II / EM;-;RDS Garcia;2024.2;Campinas;SP; R$ 11.631,40 ; +24068;Forcamp;EF II / EM;-;Escola Estadual Helena;2024.2;Indaiatuba;SP; R$ 4.809,70 ; +24069;Forcamp;EF I / EF II / EM;-;RDS Mimosa;2024.2;Campinas;SP; R$ 10.373,30 ; +24070;Forcamp;EFII;-;Colgio Almeida Junior;2024.2;Itu;SP; R$ 3.128,20 ; +24071;Forcamp;EI / EFI;-;VIVAP;2024.2;Campinas;SP; R$ 8.225,10 ; +24072;Forcamp;EF II / EM;-;EE Lucidio;2024.2;Cabreuva;SP; R$ 3.900,20 ; +24073;Forcamp;EF II / EM;-;EE Capito;2024.2;Cabreuva;SP; R$ 5.477,60 ; +24075;Forcamp;EM;-;Anglo Indaiatuba;2024.2;Indaiatuba;SP; R$ 6.908,90 ; +24076;Forcamp;EF II / EM;-;Forte Castelo;2024.2;Itu;SP; R$ 3.148,50 ; +24077;Forcamp;EM;-;Instituto Federal Salto;2024.2;Salto;SP; R$ 3.621,10 ; +24079;Forcamp;EM;-;ETECAP;2024.2;Campinas;SP; R$ 14.671,40 ; +25010;Forcamp;Superor Diversos;-;CEUNSP OPTOMETRIA ;2025.1;Salto;SP; R$ 6.434,80 ; +26001;Viva SP;Odontologia;CTT - 8176;Universidade Braz Cubas;2026.2;Mogi das Cruzes;SP; R$ - ; +24082;Perfil;Unificados;CTT - 702 UNIF CEUNSP;CEUNSP;2024.2;Itu;SP; R$ 11.360,69 ;Sim +24083;Perfil;EM;CTT - 745;Fundao Romi NEI;2024.2;Santa Barbara d' Oeste;SP; R$ 4.776,50 ; +24084;Perfil;EM / TEC;CTT - 747;Bento Quirino;2024.2;Campinas;SP; R$ 10.489,50 ; +24085;Perfil;EF II / EM;CTT - 748;Colgio Rio Branco;2024.2;Campinas;SP; R$ 23.057,00 ; +24086;Viva SP;EF II / EM;CTT - 8498;Oficina do Estudante (Baile);2024.2;Campinas;SP; R$ 6.464,40 ; +24087;Perfil;Superor Diversos;CTT - 662/681 UNIF Einsten / FHO / UNAR;Einsten / FHO / UNAR;2024.2;Araras;SP; R$ 14.521,84 ;sim +24088;Perfil;Superor Diversos;CTT - 641 UNIFICADOS;FAM - FATEC - FACP - Unisal - UNIP Limeira - Anhangueras SBO/Sumar;2024.2;Americana;SP; R$ 38.830,69 ;Sim +24089;Perfil;Superor Diversos;CTT - 679/655 UNIF - USF Med. Vet / UNIF Camp;USF;2024.2;Campinas;SP; R$ 13.166,33 ;sim +24090;Smart;EF II / EM;-;Objetivo So Pedro;2024.2;So Pedro;SP; R$ 3.843,22 ; +24091;Forcamp;EI / EFI;-;Joo e Marcela;2024.2;Campinas;SP; R$ 1.580,80 ; +24092;Arte Formaturas;Psicologia;-;FIRB;2024.1;Andradina;SP; R$ 1.986,97 ; +10000;Arte Formaturas;-;Eventos Institucionais Arte Formaturas;Arte Formaturas;-;Dracena;SP; R$ 3.682,19 ; +24093;Smart;EM;Esta turma foi cancelada, fotografamos apenas um trote;Piracicabano;2024.2;Piracicaba;SP; R$ 186,00 ; +23305;Ponta Eventos;Superor Diversos;Formandos 23.2 / 24.1;UNINTER Jundia;2023.2;Jundia;SP; R$ 2.718,20 ; +26002;Arte Formaturas;Med. Veterinria;CTT - 1145;FEA;2026.2;Andradina;SP; R$ 1.832,60 ; +25058;Arte Formaturas;Psicologia;CTT - 1170;AEMS;2025.2;Trs Lagoas;MS; R$ 400,37 ; +26003;Arte Formaturas;Odontologia;CTT - 1073;AEMS;2026.2;Trs Lagoas;MS; R$ 944,10 ; +24095;Prime;EF II / EM;-;Objetivo Exitus S.B.O;2024.2;Santa Barbara d' Oeste;SP; R$ 3.151,80 ; +24096;Prime;Superor Diversos;-;FATEC Americana;2024.1;Americana;SP; R$ 6.600,00 ; +24097;Prime;Superor Diversos;-;FATEC Guaratingueta;2024.1;Guaratingueta;SP; R$ 6.442,80 ; +24098;Viva SP;EF II / EM;-;Poliedro SJC;2024.2;So Jos do Campos;SP; R$ 33.104,71 ; +24099;Viva SP;EM;-;Culto a Ciencia;2024.2;Campinas;SP; R$ 10.933,10 ; +24119;Golden;Superor Diversos;-;FATEC SJC;2024.1;So Jos do Campos;SP; R$ 7.235,00 ; +24120;Golden;Superor Diversos;-;Anhanguera / UNOPAR;2024.1;So Jos do Campos;SP; R$ 5.325,00 ; +24103;Viva SP;EM;-;Colgio Santa Catarina;2024.2;So Paulo;SP; R$ 5.274,40 ; +24104;Golden;Tec. Diversos;-;ETEC Jacare;2024.1;Jacare;SP; R$ 3.614,60 ; +24105;Viva SP;Superor Diversos;-;IBMEC;2024.1;So Paulo;SP; R$ 6.019,63 ; +24106;Viva SP;EM;-;Adalberto Nascimento;2024.2;Campinas;SP; R$ 6.269,00 ; +24107;Perfil;Superor Diversos;CTT- 763;FAM;2024.1;Americana;SP; R$ 11.790,00 ; +24108;Festa da Beca;EF II / EM;-;Valdomiro Silveira;2024.2;Santo Andr;SP; R$ 4.773,10 ; +24109;Festa da Beca;EF II / EM;-;Angelo Mendes;2024.2;So Paulo;SP; R$ 5.456,40 ; +24110;Festa da Beca;EF II / EM;-;Fitipaldi;2024.2;So Caetano do Sul;SP; R$ 6.196,25 ; +24111;Festa da Beca;EF II / EM;-;COC;2024.2;So Caetano do Sul;SP; R$ 11.260,11 ; +24112;Festa da Beca;EF II / EM;-;Antunes;2024.2;Santo Andr;SP; R$ 10.799,20 ; +24113;Festa da Beca;EF II / EM;-;Angelo Pelegrino;2024.2;So Caetano do Sul;SP; R$ 9.583,00 ; +24115;Festa da Beca;EF II / EM;-;Generoso;2024.2;Santo Andr;SP; R$ 10.657,20 ; +24116;Festa da Beca;EF II / EM;-;Arbos;2024.2;So Caetano do Sul;SP; R$ 3.536,40 ; +24117;Festa da Beca;EF II / EM;-;Alcina Dantas;2024.2;So Caetano do Sul;SP; R$ 14.519,20 ; +24118;Perfil;Superor Diversos;CTT -764;Anhanguera Americana e S.B.O;2024.1;Americana;SP; R$ 2.692,00 ; +24121;Arte Formaturas;Superor Diversos;Eng Civil / Eng Produo;UNIESP;2024.1;Dracena;SP; R$ 1.980,00 ; +24122;Arte Formaturas;Fisioterapia;CTT - 1188;FADAP;2024.2;Tup;SP; R$ 4.274,51 ; +24123;Arte Formaturas;EM;CTT - 1210;ISAC;2024.2;Dracena;SP; R$ 12.202,58 ; +25006;Arte Formaturas;Med. Veterinria;CTT - 1171;FEA;2025.2;Andradina;SP; R$ 3.449,60 ; +25007;Arte Formaturas;Medicina;CTT - 1261;UCP;2025.2;Paraguai;SP; R$ 17.960,00 ;Sim +26004;Arte Formaturas;Biomedicina;CTT - 1251;AEMS;2026.2;Trs Lagoas;MS; R$ - ; +26005;Arte Formaturas;Enfermagem;CTT - 1234;AEMS;2026.2;Trs Lagoas;MS; R$ 3.420,50 ; +26006;Arte Formaturas;Enfermagem;CTT - 1250;FADAP;2026.2;Tup;SP; R$ 2.261,72 ; +26007;Arte Formaturas;Agronomia;CTT - 1215;FEA;2026.2;Andradina;SP; R$ 396,46 ; +26008;Arte Formaturas;Direito;CTT - 916;UNIFAI;2026.2;Adamantina;SP; R$ 3.540,44 ; +26009;Arte Formaturas;Fisioterapia;CTT - 964;UNIFAI;2026.2;Adamantina;SP; R$ - ; +26010;Arte Formaturas;Med. Veterinria;CTT - 1040;UNIFAI;2026.2;Adamantina;SP; R$ 2.272,91 ; +27101;Arte Formaturas;Agronomia;CTT - 1235;AEMS;2027.2;Trs Lagoas;MS; R$ - ; +27102;Arte Formaturas;Psicologia;CTT - 1243;FADAP;2027.2;Tup;SP; R$ 2.373,00 ; +28005;Arte Formaturas;Superor Diversos;CTT - 1268;AEMS;2028.2;Trs Lagoas;MS; R$ 499,80 ; +28002;Arte Formaturas;Agronomia;CTT - 1238;FEA;2028.2;Andradina;SP; R$ 454,79 ; +24124;Prime;Tec. Diversos;-;ETEC Trajano Camargo;2024.1;Limeira;SP; R$ 2.768,20 ; +24126;Prime;Tec. Diversos;-;ETEC Fernando Prestes;2024.2;Sorocaba;SP; R$ 16.562,80 ; +24127;Prime;Tec. Diversos;-;ETEC Monte Mor;2024.2;Monte Mor;SP; R$ 8.455,10 ; +24128;Prime;Tec. Diversos;-;ETEC Trajano Camargo;2024.2;Limeira;SP; R$ 7.780,40 ; +24129;Ponta Eventos;Superor Diversos;-;Pecege;2024.1;Piracicaba;SP; R$ 1.012,00 ; +25008;Smart;Superor Diversos;Med. Vet - Zootecnia - Eng Agronmica;UNESP;2025.2;Botucatu;SP; R$ 7.631,46 ; +26011;Viva SP;Med. Veterinria;CTT - 8067;UNISA;2026.2;So Paulo;SP; R$ 1.579,40 ; +24130;Ponta Eventos;Superor Diversos;-;FATEC Gara;2024.1;Gara;SP; R$ 4.500,00 ; +24131;Ponta Eventos;Superor Diversos;-;FATEC Botucatu;2024.1;Botucatu;SP; R$ 4.072,20 ; +24132;Ponta Eventos;Superor Diversos;-;FATEC Itapira;2024.1;Itapira;SP; R$ 3.108,79 ; +26012;Smart;Direito;-;Anhanguera / UNIMEP / FAM;2026.2;Piracicaba;SP; R$ 2.690,80 ; +26013;Smart;Direito;-;FATEP;2026.2;Piracicaba;SP; R$ 1.002,50 ; +24133;Toy SP;Superor Diversos;CTT - #1;ESPM;2024.1;So Paulo;SP; R$ 21.123,27 ; +24135;Forcamp;EF II / EM;-;Colgio Candelaria;2024.2;Indaiatuba;SP; R$ 5.658,20 ; +24136;Photum;EF I / EF II / EM;-;Anglo Itu;2024.2;Itu;SP; R$ 16.635,04 ; +25009;Smart;Psicologia;-;Anhembi Morumbi;2025.2;Piracicaba;SP; R$ 4.090,00 ; +27006;Viva SP;Medicina;T6 CTT - 8677;Unoeste;2027.2;Guaruj;SP; R$ 10.150,50 ;Sim +28003;Viva SP;Medicina;XXVI;UNIMES;2028.2;Santos;SP; R$ 25.833,40 ;Sim +24134;Viva SP;Superior Diversos;CTT - 7772;USF Camps / UNIP Bragana / UNIP Jundia;2024.2;Campinas;SP; R$ 9.669,00 ; +24137;Prime;EF I / EF II / EM;-;Objetivo Piedade;2024.2;Piedade;SP; R$ 6.408,30 ; +24138;Forcamp;EM;-;Instituto Federal Capivari;2024.2;Capivari;SP; R$ 3.755,10 ; +24139;Ponta Eventos;Tec. Diversos;-;IFSP Capivari;2024.1;Capivari;SP; R$ 1.382,60 ; +24140;Ponta Eventos;Superor Diversos;-;FATEC Piracicaba;2024.1;Piracicaba;SP; R$ 3.119,00 ; +24141;Festa da Beca;Superor Diversos;-;Metodista;2024.1;So Paulo;SP; R$ 3.262,03 ; +24142;Festa da Beca;Superor Diversos;Turmas EAD;Metodista;2024.1;So Paulo;SP; R$ 3.265,03 ; +24143;Viva SP;EM;-;ETAPA Valinhos;2024.2;Valinhos;SP; R$ 24.981,36 ;Sim +24144;Forcamp;EF II / EM;-;E.E Morada do Sol;2024.2;Indaiatuba;SP; R$ 2.960,00 ; +24145;Festa da Beca;EF I / EF II / EM;-;Colgio Paineiras;2024.2;Santo Andr;SP; R$ 4.548,90 ; +24146;Festa da Beca;EFII;-;EE Maria Luiza Ferrari Cicero;2024.2;So Bernardo do Campo;SP; R$ 4.831,70 ; +24147;Festa da Beca;Biomedicina;-;UNIFESP;2024.2;So Paulo;SP; R$ 2.234,60 ; +24148;Viva SP;EF II / EM;-;Colgio Shalom;2024.2;Campinas;SP; R$ 5.605,80 ; +24149;Forcamp;EF I / EF II / EM;Todos os seguimentos EI EFI EFII EM;RDS Swiss Park;2024.2;Campinas;SP; R$ 3.942,40 ; +24150;Forcamp;EFII;-;Anglo Renovao;2024.2;Indaiatuba;SP; R$ 3.482,70 ; +27007;Arte Formaturas;Terapia Ocupacinal;-;AEMS;2027.2;Trs Lagoas;MS; R$ 3.595,86 ; +24151;Golden;EM / TEC;-;ETEC SJC;2024.2;So Jos do Campos;SP; R$ 10.196,64 ; +24153;MVP Formaturas;Direito;UNISANTA / UNISANTOS;UNISANTOS;2024.2;Santos;SP; R$ 13.341,95 ; +24154;Ponta Eventos;Superor Diversos;-;IFSP Piracicaba;2024.2;Piracicaba;SP; R$ 2.692,10 ; +24155;Ponta Eventos;Superor Diversos;-;IFSP Capivari;2024.2;Capivari;SP; R$ 2.015,90 ; +24156;Viva SP;EF II / EM;TURMA NO FOI EFETIVADO O CONTRATO;Colgio integrado;2024.2;Jaguariuna;SP; R$ 451,70 ; +24157;Prime;Superor Diversos;-;FATEC Americana;2024.1;Americana;SP; R$ 1.650,00 ; +24158;Prime;EM / TEC;-;ETEC Sumar;2024.2;Sumar;SP; R$ 2.521,40 ; +27008;Viva SP;Odontologia;KELLY LIMA - CTT 8607;UNISA;2027.2;So Paulo;SP; R$ 4.799,20 ; +25011;Viva SP;Superor Diversos;SADE - CTT 6773;USF;2025.2;Bragana Paulista;SP; R$ 1.237,80 ; +25012;Perfil;EM;CTT - 754;Fundao Romi NEI;2025.2;Santa Barbara d' Oeste;SP; R$ 2.500,00 ; +25013;Perfil;EF II / EM;CTT - 759;Colgio Americana;2025.2;Americana;SP; R$ 1.090,00 ; +25014;Perfil;EM / TEC;CTT - 756;ETEC Polivalente;2025.2;Americana;SP; R$ 6.620,00 ; +25015;Perfil;EM / TEC;CTT- 798;ETEC Araras;2025.2;Araras;SP; R$ - ; +25016;Perfil;EF I / EF II / EM;CTT - 782;EDUQ;2025.2;Rio Claro;SP; R$ 5.989,80 ; +25017;Perfil;Superor Diversos;CTT - 753;ILUM - ESCOLA DE CINCIA E TECNOLOGIA;2025.2;Campinas;SP; R$ 322,00 ;sim +25018;Perfil;Med. Veterinria;CTT - 760;UNIFAJ;2025.2;Jaguariuna;SP; R$ 9.880,40 ;Sim +25019;Perfil;Superor Diversos;CTT - 761;UNASP;2025.2;Hortolandia ;SP; R$ - ; +25020;Perfil;Superor Diversos;UNIF - Americana CTT - 682;FAM / UNISAL / Anhangueras SOB e Sumar / Fatec.;2025.2;Americana;SP; R$ 19.920,00 ;Sim +25021;Perfil;Superor Diversos;UNIF. FHO FARMCIA / UNIF. FHO PSICO /UNIF. EINSTEIN E UNIP ;FHO / EINSTEIN E UNIP ;2025.2;Araras;SP; R$ 15.418,80 ;Sim +25022;Viva SP;EM;CTT - 8634;Porto Seguro - Valinhos;2025.2;Valinhos;SP; R$ 6.400,10 ; +24182;Perfil;Superor Diversos;CTT - 771;UNISAL Campinas;2024.2;Campinas;SP; R$ 6.859,10 ; +24183;Perfil;Superor Diversos;CTT - 769;FAM;2024.2;Americana;SP; R$ 20.051,20 ; +24159;Perfil;EI / EFI;CTT - 774;Objetivo S.B.O;2024.2;Santa Barbara d' Oeste;SP; R$ 4.920,00 ; +10002;Toy SP;-;Eventos Institucionais Toy Formaturas;TOY;-;SP;SP; R$ 8.750,40 ; +24160;Festa da Beca;EFII;;Juarez Tavora;2024.2;Santo Andr;SP; R$ 1.725,00 ; +26017;MVP Formaturas;Odontologia;-;UNIMES;2026.2;Santos;SP; R$ 12.825,20 ;Sim +24161;Arte Formaturas;EM;-;Escola Julieta;2024.2;Dracena;SP; R$ 3.465,60 ; +28004;Arte Formaturas;Direito;CTT - 1316;FIRB;2028.2;Dracena;SP; R$ 1.575,88 ; +24162;Ponta Eventos;Superor Diversos;-;FATEC Gara;2024.2;Gara;SP; R$ 9.527,20 ; +26018;Arte Formaturas;Superor Diversos;CTT - 1332 (C. Biologicas);UFMS;2026.2;Trs Lagoas;MS; R$ 3.318,99 ; +24163;Viva SP;Direito;CTT-8119 Junao Direito;EPD-ESCOLA PAULISTA DIREITO / UNISUL;2024.2;So Paulo;SP; R$ 12.585,70 ; +24164;Festa da Beca;Odontologia;-;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 5.895,40 ; +24165;Festa da Beca;Med. Veterinria;;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 4.452,50 ; +24166;Festa da Beca;Psicologia;;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 3.459,20 ; +24167;Festa da Beca;Superor Diversos;Biomed/estetica/ Logistica/ mkt;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 3.426,60 ; +24168;Festa da Beca;Superor Diversos;Comunicao;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 3.604,80 ; +24169;Festa da Beca;Direito;-;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 3.376,90 ; +24170;Festa da Beca;Superor Diversos;ADM/Comex/ Ciencias Contabeis/ RH;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 3.484,40 ; +24171;Festa da Beca;Superor Diversos;Turmas EAD;Metodista ;2024.2;So Bernardo do Campo;SP; R$ 7.415,60 ; +25025;Smart;EFII;-;Liceu Terras;2025.2;Piracicaba;SP; R$ 1.582,20 ; +25026;Viva SP;EM;-;Imaculada Campinas;2025.2;Campinas;SP; R$ 3.696,40 ; +25027;Viva SP;EF II / EM;-;Poliedro Campinas;2025.2;Campinas;SP; R$ 5.963,90 ; +25028;Smart;EFII;-;Antares;2025.2;Americana;SP; R$ 2.550,00 ; +25029;Smart;EFII;-;Objetivo Piracicaba;2025.2;Piracicaba;SP; R$ 372,60 ; +25030;Smart;EM;-;Anglo Araras;2025.2;Araras;SP; R$ 2.446,65 ; +25031;Forcamp;EF I / EF II / EM;-;RDS Garcia;2025.2;Campinas;SP; R$ 8.531,40 ; +25032;Smart;EM;-;Colgio Elite;2025.2;Piracicaba;SP; R$ 6.573,20 ; +25033;Smart;EFII;-;Dom Bosco Cidade Alta;2025.2;Piracicaba;SP; R$ 2.694,40 ; +25034;Forcamp;EF II / EM;-;Colgio Monte-Sionense;2025.2;Monte Sio;MG; R$ 3.319,90 ; +25035;Smart;EM;-;Objetivo Piracicaba;2025.2;Piracicaba;SP; R$ 6.728,70 ;sim +25036;Smart;Superor Diversos;Ed. Fisica / ADM / Pedag / Enferm;Anhanguera Piracicaba;2025.2;Piracicaba;SP; R$ 1.841,00 ; +25037;Smart;EM;-;INSA Araras;2025.2;Araras;SP; R$ 2.420,25 ; +25038;Smart;Superor Diversos;Arquitetura / Direito /Agro / Med.Vet / Nutri;UNAR;2025.2;Araras;SP; R$ - ; +25039;Smart;Superor Diversos;Biocombus/Gesto/P Quimicos;FATEC Piracicaba;2025.2;Piracicaba;SP; R$ - ; +25040;Smart;EM;-;Dom Bosco Cidade Alta;2025.2;Piracicaba;SP; R$ 3.008,80 ; +25041;Smart;Superor Diversos;Adm/Agro/ Eng. Prod / Eng. Civil;FATEP;2025.2;Piracicaba;SP; R$ - ; +24173;Viva SP;Superor Diversos;CTT - 6773 Enfer / Biomed / Farma / Fisio / Psico;USF Bragana Paulista;2024.2;Bragana Paulista;SP; R$ 4.490,80 ; +24174;Viva SP;Superor Diversos;CTT - 8110 FIAP-Eng / UAM-Sistemas / UNIP-UNIF; FIAP / UNIP / UAM;2024.2;So Paulo;SP; R$ 8.304,00 ; +24175;Viva SP;Superor Diversos;CTT-7088 Fisio / Biomed / Med. Vet;CEUSP;2024.2;Itu;SP; R$ 2.672,30 ; +24176;Arte Formaturas;Agronomia;CTT - 1334;UNIFAI;2024.2;Dracena;SP; R$ 3.374,81 ; +24177;Perfil;Superor Diversos;CTT - 780 ADM/Arquitetura/Engenharias;UNISAL Americana;2024.2;Americana;SP; R$ 7.595,00 ; +24178;Ponta Eventos;EM / TEC;Integrado Q+I / RH + Alimentos ;IFSP Capivari;2024.2;Capivari;SP; R$ 4.250,00 ; +24179;Ponta Eventos;Superor Diversos;;IFSP Capivari;2024.2;Capivari;SP; R$ 2.411,00 ; +24180;Ponta Eventos;Superor Diversos;;FATEC Botucatu;2024.2;Botucatu;SP; R$ 5.042,00 ; +24181;Golden;Superor Diversos;;FATEC SJC;2024.2;So Jos dos Campos;SP; R$ 5.350,00 ; +24184;Arte Formaturas;Agronomia;-;FEA;2024.2;Adamantina;SP; R$ 1.185,00 ; +24185;Fbio Ribeiro;Direito;-;ISCA;2024.2;Limeira;SP; R$ 2.037,00 ; +25042;Toy SP;Unificados;CTT - #4;ESPM;2025.2;So Paulo;SP; R$ 8.155,70 ; +26019;Toy SP;Direito;CTT - #10;FDSBC ;2026.2;So Bernardo do Campo;SP; R$ - ; +27019;Toy SP;Med. Veterinria;CTT - #13 - 27.1 + 27.2;UAM;2027.2;So Paulo;SP; R$ 5.014,20 ;Sim +25043;Toy SP;Superor Diversos;CCT - #5 - 25.2 + 26.1;PUC;2025.2;So Paulo;SP; R$ 580,00 ; +24186;Perfil;Superor Diversos;CTT - 781;Anhanguera Sumar;2024.2;Sumar;SP; R$ 2.620,00 ; +25044;Perfil;EF II / EM;CTT - 778;Objetivo S.B.O;2025.2;Santa Barbara d' Oeste;SP; R$ 3.430,00 ; +25045;Perfil;EM / TEC;CTT - 779;Cotil;2025.2;Limeira;SP; R$ - ; +25046;Perfil;Superor Diversos;CTT - 696/726;UNIF. Campinas;2025.2;Campinas;SP; R$ 13.194,30 ;Sim +25047;Perfil;EM;CTT - 757;ETEC Polivalente;2025.2;Americana;SP; R$ - ; +25048;Perfil;Superor Diversos;CTT - 720 Psico/Biomed;FAM;2025.2;Americana;SP; R$ - ; +25049;Perfil;Superor Diversos;CTT - 730 UNIF. Sade;CEUSP;2025.2;Itu;SP; R$ - ; +24187;Viva SP;Superor Diversos;-;IBMEC;2024.2;So Paulo;SP; R$ 6.471,90 ; +25050;Viva SP;Med. Veterinria;CTT - 8117;USP;2025.2;So Paulo;SP; R$ 870,00 ; +25051;Viva SP;Direito;CTT - 8241;UNICID;2025.2;So Paulo;SP; R$ 1.740,00 ;Sim +25052;Smart;EFII;-;Anglo Portal;2025.2;Piracicaba;SP; R$ 2.122,30 ; +25053;Smart;EFII;-;Anglo Cidade Alta;2025.2;Piracicaba;SP; R$ 1.656,20 ; +25054;Perfil;EF I / EF II;CTT - 784;Colgio Dom Pedro;2025.2;Americana;SP; R$ 4.798,62 ; +25055;Perfil;EFII;CTT - 786;Imaculada Campinas;2025.2;Campinas;SP; R$ 7.371,90 ; +25056;Perfil;EF I / EF II / EM;CTT - 785;Colgio IESC - Nova Odessa;2025.2;Campinas;SP; R$ 1.307,60 ; +25057;Perfil;EFII;CTT - 783 - Colgio So Jos Participa do Baile;COC Paulinia;2025.2;Paulinia;SP; R$ 9.078,20 ; +24188;Prime;Superior Diversos;-;FATEC Ribeiro Preto;2024.2;Ribeiro Preto;SP; R$ 7.329,10 ; +24189;Prime;Superior Diversos;-;FATEC Mogi Mirim;2024.2;Mogi Mirim;SP; R$ 3.225,60 ; +24190;Prime;Superior Diversos;-;FATEC Caparicuiba;2024.2;Carapicuiba;SP; R$ 5.712,75 ; +24191;Prime;Superior Diversos;-;FATEC Taquaritinga;2024.2;Taquaritnga;SP; R$ 7.550,00 ; +24192;Prime;Superior Diversos;-;FATEC Santana de Parnaiba;2024.2;Santana de Parnaiba;SP; R$ 3.624,90 ; +24193;Prime;Superior Diversos;-;IFSP So Roque;2024.2;So Roque;SP; R$ 3.271,34 ; +24194;Prime;Superior Diversos;-;FATEC So Carlos;2024.2;So Carlos;SP; R$ 4.838,80 ; +24195;Prime;Superior Diversos;-;FATEC Sumar;2024.2;Sumar;SP; R$ 2.870,30 ; +24196;Prime;Superior Diversos;-;FATEC Jundia;2024.2;Jundia;SP; R$ 5.823,90 ; +25059;Viva SP;EF II / EM;-;Escola Comunitaria de Campinas;2025.2;Campinas;SP; R$ 4.328,20 ; +24197;Golden;Superior Diversos;-;Anhanguera / UNOPAR;2024.2;So Jos do Campos;SP; R$ 8.780,00 ; +24198;Toy SP;Superior Diversos;CTT - #2;ESPM;2024.2;So Paulo;SP; R$ 12.401,90 ; +25060;Perfil;EF I / EF II / EM;-;Oficina do Estudante;2024.2;Piracicaba;SP; R$ 16.543,80 ; +24199;Ponta Eventos;Superior Diversos;-;UNINTER Jundia;2024.2;Jundia;SP; R$ 2.750,80 ; +24206;Ponta Eventos;Superior Diversos;-;FATEC Itapira;2024.2;Itapira;SP; R$ 2.843,00 ; +25061;Smart;EM;-;INSA Araras;2025.2;Araras;SP; R$ - ; +25062;Perfil;EF II / EM;CTT - 788;Colgio Educativa;2025.2;Sumar;SP; R$ 5.747,50 ; +26020;Perfil;Superior Diversos;CTT - 703;UNIF. Campinas;2026.2;Campinas;SP; R$ - ; +26021;Perfil;Superior Diversos;CTT - 718;UNIF. Piracicaba;2026.2;Piracicaba;SP; R$ - ; +26022;Perfil;Superior Diversos;CTT - 731;UNIF. FHO Sade;2026.2;Araras;SP; R$ - ; +26023;Perfil;Superior Diversos;CTT - 739;UNIF. Limeira;2026.2;Limeira;SP; R$ - ; +26024;Perfil;Biomedicina;CTT - 766;USF;2026.2;Campinas;SP; R$ 955,30 ; +26025;Perfil;Superior Diversos;CTT - 767;UNASP;2026.2;Hortolandia ;SP; R$ - ; +26026;Perfil;Engenharia;CTT - 777;FHO;2026.2;Araras;SP; R$ - ; +26027;Perfil;Superior Diversos;CTT - 683 Turma 26.2 e 27.1;UNIF. Americana;2026.2;Americana;SP; R$ - ; +27021;Perfil;Superior Diversos;CTT - 728;FAM - Sade;2027.2;Americana;SP; R$ - ; +27022;Perfil;Superior Diversos;CTT - 749;FAM - Sade;2027.2;Americana;SP; R$ - ; +25063;Smart;EFII;-;KOELLE;2025.2;Rio Claro;SP; R$ 1.344,60 ;Sim +25064;Smart;EM;-;KOELLE;2025.2;Rio Claro;SP; R$ 15.008,20 ;Sim +25065;Smart;EM;-;Liceu Terras;2025.2;Piracicaba;SP; R$ 5.335,60 ; +25066;Smart;EM;-;ETAPA Valinhos;2025.2;Valinhos;SP; R$ 402,50 ; +24207;Prime;Superior Diversos;-;FATEC Araras;2024.2;Araras;SP; R$ 3.019,70 ; +25067;Prime;EFII;-;Bandeirante Americana;2025.2;Americana;SP; R$ 4.825,00 ; +25068;Forcamp;EM;-;Colgio Anglo Salto;2025.2;Salto;SP; R$ - ; +25069;Forcamp;EM;-;Colgio Conquista;2025.2;Indaiatuba;SP; R$ 7.564,30 ; +25070;Forcamp;EM;-;Colgio Rodin;2025.2;Indaiatuba;SP; R$ 1.810,00 ; +25071;Forcamp;EFII;-;Colgio Rodin;2025.2;Indaiatuba;SP; R$ 2.597,80 ; +25072;Forcamp;EFII;-;Anglo Salto;2025.2;Salto;SP; R$ 2.542,20 ; +25073;Forcamp;EFII;-;Colgio Escala;2025.2;Indaiatuba;SP; R$ 2.740,00 ; +25074;Forcamp;EFII;-;Divino Cabreuva;2025.2;Cabreuva;SP; R$ 393,00 ; +25075;Forcamp;EF II / EM;-;E.E Helena;2025.2;Indaiatuba;SP; R$ 2.016,00 ; +25076;Forcamp;EM;-;Colgio Divino;2025.2;Indaiatuba;SP; R$ - ; +25077;Forcamp;EF II / EM;-;E.E Aurora;2025.2;Indaiatuba;SP; R$ 830,70 ; +25078;Forcamp;EFII;-;Colgio Divino;2025.2;Itu;SP; R$ 3.214,20 ; +25079;Forcamp;EF II / EM;-;SESI Salto;2025.2;Salto;SP; R$ 2.249,00 ; +25080;Forcamp;EFII;-;Colgio Conquista;2025.2;Indaiatuba;SP; R$ - ; +25081;Forcamp;EF II / EM;-;Colgio Almeida JR;2025.2;Indaiatuba;SP; R$ 1.114,40 ; +25082;Forcamp;EF II / EM;-;Colgio Forte Castelo;2025.2;Itu;SP; R$ 1.114,80 ; +25083;Forcamp;EF II / EM;-;Colgio Capito;2025.2;Indaiatuba;SP; R$ 1.521,70 ; +25084;Forcamp;EF II / EM;-;Colgio Lucidio;2025.2;Cabreuva;SP; R$ 2.114,60 ; +25085;Forcamp;EF II / EM;-;Colgio Candelaria;2025.2;Indaiatuba;SP; R$ 3.009,60 ; +25086;Forcamp;EM;-;Anglo Indaiatuba;2025.2;Indaiatuba;SP; R$ 3.063,00 ; +25087;Forcamp;EFII;-;Colgio Anglo Renovao;2025.2;Indaiatuba;SP; R$ - ; +25088;Forcamp;EF II / EM;-;E.E Annunziatta;2025.2;Indaiatuba;SP; R$ 2.061,70 ; +25089;Forcamp;EF II / EM;-;SESI Amparo;2025.2;Amparo;SP; R$ 789,80 ; +25090;Forcamp;EFII;-;Colgio Elite;2025.2;Indaiatuba;SP; R$ 1.058,20 ; +25091;Forcamp;EF I / EF II / EM;-;RDS Mimosa;2025.2;Campinas;SP; R$ 4.897,90 ; +25092;Forcamp;EF I / EF II;-;VIVAP;2025.2;Campinas;SP; R$ 3.709,50 ; +25093;Forcamp;EFI (5 ano);-;Joo e Marcela;2025.2;Campinas;SP; R$ 490,00 ; +25094;Forcamp;EF I / EF II / EM;-;RDS Swiss Park;2025.2;Campinas;SP; R$ 2.150,80 ; +25095;Viva SP;Superior Diversos;Juno CTT - 8421 ; Uniso / Facens / ceusnp;2025.2;Sorocaba;SP; R$ 2.433,14 ; +25096;Viva SP;Odontologia;CTT - 8097 sthefany ;USJT-UBC-Unip - Metodista;2025.2;Santos;SP; R$ 11.333,80 ;Sim +26028;Viva SP;Odontologia;-;UNIP;2026.2;So Paulo;SP; R$ 1.906,30 ; +25097;Prime;EM / TEC;-;ETEC Monte Mor;2025.2;Monte Mor;SP; R$ 2.226,10 ; +25098;Prime;EM / TEC;-;ETEC Fernando Prestes;2025.1;Sorocaba;SP; R$ 7.378,32 ; +25099;Toy SP;Superior Diversos;CTT - #3;ESPM;2025.1;So Paulo;SP; R$ 24.705,07 ; +26029;Toy SP;Enfermagem;CTT - #11;UNIFESP;2026.2;So Paulo;SP; R$ 2.670,00 ;Sim +27023;Toy SP;Superior Diversos;Unificados CTT - #14;Einsten;2027.2;So Paulo;SP; R$ 7.859,10 ;Sim +25113;Smart;EM;-;Claretiano;2025.2;Rio Claro;SP; R$ 5.475,00 ;Sim +25114;Forcamp;EF II / EM;-;Meck Genius;2025.2;Jaguariuna;SP; R$ 1.307,30 ; +25115;Viva SP;EM;-;Colgio Elite Satana;2025.2;So Paulo;SP; R$ 2.874,80 ; +25116;;;;;;;;; +25117;;;;;;;;; +25118;Viva SP;EM;-;Porto Seguro - Panamby;2025.2;So Paulo;SP; R$ 3.634,60 ; +25119;Viva SP;EF II / EM;-;Poliedro SJC;2025.2;So Jos dos Campos;SP; R$ 21.670,94 ;Sim +28006;Arte Formaturas;Unificados;CTT - 1355 - ADM/C. Contbeis/Direito;Reges;2028.2;Dracena;SP; R$ 760,00 ; +27024;Arte Formaturas;Terapia Ocupacinal;CTT - 1249;AEMS;2027.2;Trs Lagoas;MS; R$ 400,00 ; +26030;Arte Formaturas;Unificados;CTT - 1295 - Unificados Andradina;FIRB / FAE;2026.2;Andradina;SP; R$ - ; +25120;Arte Formaturas;Educao Fsica;CTT - 1351;FAI;2025.2;Adamantina;SP; R$ 1.245,00 ; +25107;Viva SP;EF II / EM;-;ETAPA Valinhos;2025.2;Valinhos;SP; R$ 16.031,40 ;Sim +25121;Perfil;Unificados;CTT - 796;UNISAL Americana;2025.1;Americana;SP; R$ 955,00 ; +25122;Perfil;EF I / EF II / EM;CTT - 795;COLGIO METROPOLITAN PAULINENSE ;2025.2;Paulinia;SP; R$ 3.239,70 ; +27025;Smart;Direito;-;Anhanguera ;2027.2;Piracicaba;SP; R$ 733,40 ; +25123;Smart;EFI (5 ano);-;Maple Bear;2025.2;Piracicaba;SP; R$ 230,00 ; +26031;Smart;EFII;8 Ano 2025.2;Maple Bear;2026.2;Piracicaba;SP; R$ 230,00 ; +26032;Smart;EM;2 Ano EM 2025.2;Anglo Portal;2026.2;Piracicaba;SP; R$ - ; +25124;Prime;EM;-;ETEC Fernando Prestes;2025.2;Sorocaba;SP; R$ 7.511,98 ; +25125;Prime;Tec. Diversos;-;ETEC Trajano Camargo;2025.1;Limeira;SP; R$ 3.762,30 ; +25126;Golden;EM / TEC;-;ETEC Jacare;2025.1;Jacare;SP; R$ 3.647,14 ; +25127;Golden;Superior Diversos;-;FATEC SJC;2025.1;So Jos dos Campos;SP; R$ 5.915,00 ; +26033;Arte Formaturas;Psicologia;CTT - 1356;FADAP;2026.2;Dracena;SP; R$ 1.366,00 ; +25128;Prime;EM / TEC;-;ETEC Monte Mor;2025.1;Monte Mor;SP; R$ 910,00 ; +25129;Perfil;Superior Diversos;CTT - 800;FAM;2025.1;Americana;SP; R$ 7.955,00 ; +25130;Prime;Superior Diversos;-;FATEC Sumar;2025.1;Sumar;SP; R$ 2.919,70 ; +25131;Prime;Superior Diversos;-;FATEC Ipiranga ;2025.1;So Paulo;SP; R$ 4.990,30 ; +25132;Prime;Superior Diversos;-;FATEC Americana;2025.1;Americana;SP; R$ 4.810,00 ; +25133;Prime;Superior Diversos;-;FATEC Santana de Parnaiba;2025.1;Santana de Parnaiba;SP; R$ 3.293,90 ; +25134;Prime;Superior Diversos;-;FATEC Guaratingueta;2025.1;Guaratingueta;SP; R$ 6.380,00 ; +25135;Prime;Superior Diversos;-;FATEC So Carlos;2025.1;So Carlos;SP; R$ 3.199,00 ; +25136;Prime;Superior Diversos;-;FATEC Mogi Mirim;2025.1;Mogi Mirim;SP; R$ 3.643,70 ; +25137;Prime;Superior Diversos;-;FATEC Americana;2025.1;Americana;SP; R$ 5.332,20 ; +25138;Prime;EM;-;ETEC Trajano Camargo;2025.2;Limeira;SP; R$ 4.129,80 ; +25139;Toy SP;Superior Diversos;CTT - #18;FAAP;2025.1;So Paulo;SP; R$ 12.140,20 ; +26034;Toy SP;Superior Diversos;CTT - #9 Unificados;ESPM;2026.1;So Paulo;SP; R$ - ; +26035;Toy SP;Superior Diversos;CTT - # 16 Unificados 26.2 e 27.1;FAAP;2026.2;So Paulo;SP; R$ - ; +26036;Toy SP;Superior Diversos;CTT - # 17 Unificafos 26.1 e 26.2;FOC;2026.2;So Paulo;SP; R$ - ; +10003;Festa da Beca;-;-;Eventos Institucionais ;-;-;SP; R$ 969,85 ; +25140;Alpha Digital;EI;-;Anglo Pouso Alegre;2025.2;Pouso Alegre;MG; R$ 8.246,90 ; +25141;Alpha Digital;EM;-;Anglo Pouso Alegre;2025.3;Pouso Alegre;MG; R$ 1.987,00 ; +25142;Alpha Digital;EI / EFI;-;Cezanne;2025.2;Americana;SP; R$ 4.030,00 ; +25143;Alpha Digital;EF I / EF II / EM;-;Gibram;2025.2;So Paulo;SP; R$ 7.097,00 ; +25144;Alpha Digital;EF II / EM;-;Notre Dame;2025.2;Valinhos;SP; R$ 10.538,80 ; +25145;Alpha Digital;EF II / EM;-;Objetivo Rio Claro;2025.2;Rio Claro;SP; R$ 7.226,50 ; +25146;Alpha Digital;EF II / EM;-;Sagrado Pompeia;2025.2;So Paulo;SP; R$ 6.310,30 ; +25147;Alpha Digital;EFII;-;Santa Marcelina;2025.2;So Paulo;SP; R$ 3.087,10 ; +25148;Alpha Digital;EF II / EM;-;So Jos Campinas;2025.2;Campinas;SP; R$ 4.934,80 ; +27026;Arte Formaturas;Agronomia;-;FEA;2027.2;Andradina;SP; R$ 2.156,00 ; +27027;Arte Formaturas;Enfermagem;-;UFMS;2027.2;Trs Lagoas;MS; R$ 756,10 ; +25149;Viva SP;Superior Diversos;Med. Vet - Biomed CTT - 8132/8395 ;UNICSUL;2025.2;So Paulo;SP; R$ 1.048,20 ; +25150;Viva SP;Superior Diversos;-;IBMEC;2025.1;So Paulo;SP; R$ 6.700,40 ; +26037;Arte Formaturas;Superior Diversos;CTT - 1331 (Enfer/Engharia);FIRB+FEA;2026.2;Andradina;SP; R$ 516,00 ; +27028;Arte Formaturas;Superior Diversos;CTT - 1319 (Comput/Pedagogia);FAI;2027.2;Dracena;SP; R$ 895,00 ; +25151;RUB;Superior Diversos;CTT - 15;FASM;2025.1;So Paulo;SP; R$ - ; +26038;RUB;Superior Diversos;CTT - 17;FASM;2026.2;So Paulo;SP; R$ 881,00 ; +25152;RUB;Superior Diversos;CTT - 130;FGV;2025.1;So Paulo;SP; R$ 23.162,50 ; +25153;RUB;Superior Diversos;CTT - 131;FGV;2025.2;So Paulo;SP; R$ 663,40 ; +25154;RUB;Superior Diversos;CTT - 25;INSPER;2025.1;So Paulo;SP; R$ 43.315,70 ;sim +26039;RUB;Arquitetura / Urbanismo;CTT - Aurea;Mackenzie FAU;2026.1;So Paulo;SP; R$ - ; +26040;RUB;Odontologia;CTT - Tiralentes;UNICID;2026.2;So Paulo;SP; R$ - ; +26041;RUB;Enfermagem;-;Albert Einstein;2026.2;So Paulo;SP; R$ 440,00 ; +25155;RUB;Superior Diversos;-;UNIP;2025.2;So Paulo;SP; R$ - ; +25156;RUB;Psicologia;-;PUC;2025.2;So Paulo;SP; R$ 6.275,00 ;Sim +25157;RUB;Superior Diversos;-;ESPM;2025.1;So Paulo;SP; R$ 4.088,20 ;Sim +25158;RUB;Superior Diversos;CTT - C25;CASPER;2025.2;So Paulo;SP; R$ 2.588,75 ; +25159;RUB;Superior Diversos;CTT - Mack Amnsia;Mackenzie;2025.2;So Paulo;SP; R$ 4.620,00 ;sim +25160;RUB;Superior Diversos;CTT - Macklandia;Mackenzie CCSA;2025.1;So Paulo;SP; R$ 10.949,50 ; +27029;RUB;Medicina;CTT - T30;So Camilo;2027.1;So Paulo;SP; R$ 5.805,10 ; +28007;RUB;Medicina;CTT - T32;So Camilo;2028.1;So Paulo;SP; R$ 2.636,40 ; +28008;RUB;Medicina;CTT - Med Teso;Santo Amaro;2028.1;So Paulo;SP; R$ 8.173,50 ;sim +25161;RUB;Superior Diversos;CTT - Cinesia;UNICID;2025.2;So Paulo;SP; R$ 10.824,30 ; +25162;RUB;Arquitetura / Urbanismo;CTT - 321;Mackenzie FAU;2025.2;So Paulo;SP; R$ 579,60 ; +27030;;;;;;;SP; R$ - ; +25163;Smart;EFII;-;Colgio Elite;2025.2;Piracicaba;SP; R$ 2.189,70 ; +25164;Smart;EFI (5 ano);-;Colgio Elite;2025.2;Piracicaba;SP; R$ - ; +25165;Prime;Superior Diversos;-;FATEC Ribeiro Preto;2025.1;Ribeiro Preto;SP; R$ 5.852,20 ; +25166;Ponta Eventos;Superior Diversos;Turma 25.1 e 25.2 Baile antes das colaes;IFSP Piracicaba;2025.2;Piracicaba;SP; R$ 5.451,10 ; +25167;Ponta Eventos;Superior Diversos;-;FATEC Botucatu;2025.1;Botucatu;SP; R$ 4.278,42 ; +25168;Ponta Eventos;Superior Diversos;-;FATEC Itapira;2025.1;Itapira;SP; R$ 3.110,40 ; +25169;Ponta Eventos;Superior Diversos;-;FATEC Piracicaba;2025.1;Piracicaba;SP; R$ 3.301,50 ; +27032;Smart;Direito;-;FATEP Piracicaba;2027.2;Piracicaba;SP; R$ 1.720,76 ; +27033;Smart;Direito;-;UNIMEP;2027.2;Piracicaba;SP; R$ - ; +25170;Ponta Eventos;Tec. Diversos;-;IFSP Capivari;2025.1;Campinas;SP; R$ 1.001,00 ; +25171;RUB;Superior Diversos;Link Owners;Link School;2025.1;So Paulo;SP; R$ 21.430,00 ; +25172;Viva SP;Biomedicina;Juno CTT - 8304;UNISANTA;2025.2;Santos;SP; R$ - ; +28009;Viva SP;Med. Veterinria;CTT - 9273;USJT;2028.2;Santos;SP; R$ 1.765,00 ; +28010;Viva SP;Medicina;CTT - ;FAJ;2028.2;Jaguariuna;SP; R$ 5.420,00 ; +25173;Viva SP;Superior Diversos;CTT - 8251;Metodista;2025.2;So Paulo;SP; R$ 490,00 ; +26042;Viva SP;Educao Fsica;CTT - 9140;PMSP;2026.1;So Paulo;SP; R$ 580,00 ; +25174;Arte Formaturas;Enfermagem;CTT - 1388;FEA;2025.2;Adamantina;SP; R$ 1.205,00 ; +25175;Arte Formaturas;Educao Fsica;CTT - 1351;FAI;2025.2;Dracena;SP; R$ 1.035,00 ; +25176;RUB;EF II / EM;-;Colgio CONSA;2025.2;So Paulo;SP; R$ 1.730,00 ; +25177;Ponta Eventos;Superior Diversos;-;FATEC Gara;2025.1;Gara;SP; R$ 4.128,80 ; +25178;Ponta Eventos;Superior Diversos;-;IFSP Capivari;2025.1;Capivari;SP; R$ 414,00 ; +25179;Golden;Superior Diversos;-;Anhanguera / UNOPAR;2025.1;So Jos dos Campos;SP; R$ 5.290,00 ; +25180;Prime;Superior Diversos;-;UNIP Osasco;2025.1;Osasco;SP; R$ 1.795,00 ; +25181;Festa da Beca;Superior Diversos;;Metodista;2025.1;So Bernardo do Campo;SP; R$ 8.300,90 ; +25182;Forcamp;EM;-;ETECAP;2025.2;Campinas;SP; R$ 6.071,40 ; +25183;Ponta Eventos;EM / TEC;-;E.E Elias Mello Ayres;2025.2;Piracicaba;SP; R$ 1.058,00 ; +25184;MVP Formaturas;Direito;-;UNISANTOS;2025.2;Santos;SP; R$ 8.676,30 ;Sim +25185;MVP Formaturas;Odontologia;-;UNISANTA;2025.2;Santos;SP; R$ - ; +25186;Viva SP;Superior Diversos;Juno CTT - 8742;Senac- Unicsul - FMU;2025.2;So Paulo;SP; R$ 1.354,30 ; +10004;RUB;-;-;Eventos Institucionais ;-;So Paulo;SP; R$ 290,00 ; +27034;Smart;Superior Diversos;Engenharias;UNARARAS;2027.2;Araras;SP; R$ 650,00 ; +27035;Viva SP;Odontologia;-;UNISA;2027.1;So Paulo;SP; R$ 1.835,00 ; +25187;Arte Formaturas;Direito;CTT - 1352;Reges;2025.2;Dracena;SP; R$ 1.163,30 ; +27036;Arte Formaturas;Educao Fsica;CTT - 1309;FEA;2027.2;Adamantina;SP; R$ 1.141,00 ; +25188;Viva SP;R.I;CTT - 8742;USP;2025.2;So Paulo;SP; R$ 690,00 ; +26043;Viva SP;Med. Veterinria;CTT - 8897;FMU;2026.1;So Paulo;SP; R$ 650,00 ; +25189;Viva SP;Superior Diversos;Direito e Saude / CTT - 7973;UNIP;2025.2;So Paulo;SP; R$ - ; +25190;Viva SP;Superior Diversos;CTT - 8100;UNIANCHIETA;2025.2;So Paulo;SP; R$ - ; +25191;RUB;EF II / EM;;Colgio Dante;2025.2;So Paulo;SP; R$ - ; +25192;RUB;EF II / EM;;Colgio Consept;2025.2;So Paulo;SP; R$ - ; +29001;Arte Formaturas;Direito;;FIRB;2029.2;Dracena;SP; R$ 360,00 ; +25193;Festa da Beca;-;-;Valdomiro Silveira;2025.2;So Paulo;SP; R$ - ; +25194;Festa da Beca;-;-;Kennedy;2025.2;So Paulo;SP; R$ - ; +25195;Festa da Beca;-;-;Juarez Tavora;2025.2;So Paulo;SP; R$ - ; +25196;Festa da Beca;-;-;EE Prof Cristina Fitipaldi;2025.2;So Paulo;SP; R$ - ; +25197;Festa da Beca;-;-;COC;2025.2;So Paulo;SP; R$ - ; +25198;Festa da Beca;-;-;EE Angelo mendes;2025.2;So Paulo;SP; R$ - ; +25199;Festa da Beca;-;-;Malu;2025.2;So Paulo;SP; R$ - ; +25200;Festa da Beca;-;-;Paulo Emilio;2025.2;So Paulo;SP; R$ - ; +25202;Festa da Beca;-;-;Paineiras;2025.2;So Paulo;SP; R$ - ; +25203;Festa da Beca;-;-;Oscavo;2025.2;So Paulo;SP; R$ - ; +25204;Festa da Beca;-;-;EE Santa Dalmolin Demarchi;2025.2;So Paulo;SP; R$ - ; +25205;Festa da Beca;-;-;Antunes e Paulo Emilio;2025.2;So Paulo;SP; R$ - ; diff --git a/backend/internal/agenda/handler.go b/backend/internal/agenda/handler.go index e8fab06..51dca7f 100644 --- a/backend/internal/agenda/handler.go +++ b/backend/internal/agenda/handler.go @@ -161,3 +161,150 @@ func (h *Handler) Delete(c *gin.Context) { c.Status(http.StatusNoContent) } + +// AssignProfessional godoc +// @Summary Assign professional to agenda +// @Tags agenda +// @Router /api/agenda/{id}/professionals [post] +func (h *Handler) AssignProfessional(c *gin.Context) { + idParam := c.Param("id") + agendaID, err := uuid.Parse(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) + return + } + + var req struct { + ProfessionalID string `json:"professional_id" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) + return + } + + profID, err := uuid.Parse(req.ProfessionalID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) + return + } + + if err := h.service.AssignProfessional(c.Request.Context(), agendaID, profID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atribuir profissional: " + err.Error()}) + return + } + + c.Status(http.StatusOK) +} + +// RemoveProfessional godoc +// @Summary Remove professional from agenda +// @Tags agenda +// @Router /api/agenda/{id}/professionals/{profId} [delete] +func (h *Handler) RemoveProfessional(c *gin.Context) { + idParam := c.Param("id") + agendaID, err := uuid.Parse(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) + return + } + + profIdParam := c.Param("profId") + profID, err := uuid.Parse(profIdParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) + return + } + + if err := h.service.RemoveProfessional(c.Request.Context(), agendaID, profID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao remover profissional: " + err.Error()}) + return + } + + c.Status(http.StatusNoContent) +} + +// GetProfessionals godoc +// @Summary Get professionals assigned to agenda +// @Tags agenda +// @Router /api/agenda/{id}/professionals [get] +func (h *Handler) GetProfessionals(c *gin.Context) { + idParam := c.Param("id") + agendaID, err := uuid.Parse(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) + return + } + + profs, err := h.service.GetAgendaProfessionals(c.Request.Context(), agendaID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao buscar profissionais: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, profs) +} + +// UpdateStatus godoc +// @Summary Update agenda status +// @Tags agenda +// @Router /api/agenda/{id}/status [patch] +func (h *Handler) UpdateStatus(c *gin.Context) { + idParam := c.Param("id") + agendaID, err := uuid.Parse(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) + return + } + + var req struct { + Status string `json:"status" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) + return + } + + agenda, err := h.service.UpdateStatus(c.Request.Context(), agendaID, req.Status) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar status: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, agenda) +} + +// UpdateAssignmentStatus godoc +// @Summary Update professional assignment status +// @Tags agenda +// @Router /api/agenda/{id}/professionals/{profId}/status [patch] +func (h *Handler) UpdateAssignmentStatus(c *gin.Context) { + idParam := c.Param("id") + agendaID, err := uuid.Parse(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de agenda inválido"}) + return + } + + profIdParam := c.Param("profId") + profID, err := uuid.Parse(profIdParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID de profissional inválido"}) + return + } + + var req struct { + Status string `json:"status" binding:"required"` + Reason string `json:"reason"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Dados inválidos: " + err.Error()}) + return + } + + if err := h.service.UpdateAssignmentStatus(c.Request.Context(), agendaID, profID, req.Status, req.Reason); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Erro ao atualizar status: " + err.Error()}) + return + } + + c.Status(http.StatusOK) +} diff --git a/backend/internal/agenda/service.go b/backend/internal/agenda/service.go index 0db2174..ca691df 100644 --- a/backend/internal/agenda/service.go +++ b/backend/internal/agenda/service.go @@ -2,6 +2,7 @@ package agenda import ( "context" + "encoding/json" "time" "photum-backend/internal/db/generated" @@ -44,6 +45,17 @@ type CreateAgendaRequest struct { PreVenda bool `json:"pre_venda"` } +type Assignment struct { + ProfessionalID string `json:"professional_id"` + Status string `json:"status"` + MotivoRejeicao *string `json:"motivo_rejeicao"` +} + +type AgendaResponse struct { + generated.ListAgendasRow + ParsedAssignments []Assignment `json:"assignments"` +} + func (s *Service) CalculateStatus(fotoFaltante, recepFaltante, cineFaltante int32) string { if fotoFaltante < 0 || recepFaltante < 0 || cineFaltante < 0 { return "ERRO" @@ -92,59 +104,86 @@ func (s *Service) Create(ctx context.Context, userID uuid.UUID, req CreateAgenda return s.queries.CreateAgenda(ctx, params) } -func (s *Service) List(ctx context.Context, userID uuid.UUID, role string) ([]generated.ListAgendasRow, error) { +func (s *Service) List(ctx context.Context, userID uuid.UUID, role string) ([]AgendaResponse, error) { + var rows []generated.ListAgendasRow + var err 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}) + listRows, 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, + // Convert ListAgendasByUserRow to ListAgendasRow manually + for _, r := range listRows { + rows = append(rows, 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, + Status: r.Status, + FotNumero: r.FotNumero, + Instituicao: r.Instituicao, + CursoNome: r.CursoNome, + EmpresaNome: r.EmpresaNome, + AnoSemestre: r.AnoSemestre, + ObservacoesFot: r.ObservacoesFot, + TipoEventoNome: r.TipoEventoNome, + AssignedProfessionals: r.AssignedProfessionals, }) } - return result, nil + } else { + rows, err = s.queries.ListAgendas(ctx) + if err != nil { + return nil, err + } } - return s.queries.ListAgendas(ctx) + var response []AgendaResponse + for _, row := range rows { + var assignments []Assignment + if row.AssignedProfessionals != nil { + bytes, ok := row.AssignedProfessionals.([]byte) + if !ok { + str, ok := row.AssignedProfessionals.(string) + if ok { + bytes = []byte(str) + } + } + if bytes != nil { + json.Unmarshal(bytes, &assignments) + } + } + response = append(response, AgendaResponse{ + ListAgendasRow: row, + ParsedAssignments: assignments, + }) + } + + return response, nil } func (s *Service) Get(ctx context.Context, id uuid.UUID) (generated.Agenda, error) { @@ -186,3 +225,42 @@ func (s *Service) Update(ctx context.Context, id uuid.UUID, req CreateAgendaRequ func (s *Service) Delete(ctx context.Context, id uuid.UUID) error { return s.queries.DeleteAgenda(ctx, pgtype.UUID{Bytes: id, Valid: true}) } + +func (s *Service) AssignProfessional(ctx context.Context, agendaID uuid.UUID, profID uuid.UUID) error { + params := generated.AssignProfessionalParams{ + AgendaID: pgtype.UUID{Bytes: agendaID, Valid: true}, + ProfissionalID: pgtype.UUID{Bytes: profID, Valid: true}, + } + return s.queries.AssignProfessional(ctx, params) +} + +func (s *Service) RemoveProfessional(ctx context.Context, agendaID uuid.UUID, profID uuid.UUID) error { + params := generated.RemoveProfessionalParams{ + AgendaID: pgtype.UUID{Bytes: agendaID, Valid: true}, + ProfissionalID: pgtype.UUID{Bytes: profID, Valid: true}, + } + return s.queries.RemoveProfessional(ctx, params) +} + +func (s *Service) GetAgendaProfessionals(ctx context.Context, agendaID uuid.UUID) ([]generated.GetAgendaProfessionalsRow, error) { + return s.queries.GetAgendaProfessionals(ctx, pgtype.UUID{Bytes: agendaID, Valid: true}) +} + +func (s *Service) UpdateStatus(ctx context.Context, agendaID uuid.UUID, status string) (generated.Agenda, error) { + params := generated.UpdateAgendaStatusParams{ + ID: pgtype.UUID{Bytes: agendaID, Valid: true}, + Status: pgtype.Text{String: status, Valid: true}, + } + return s.queries.UpdateAgendaStatus(ctx, params) +} + +func (s *Service) UpdateAssignmentStatus(ctx context.Context, agendaID, professionalID uuid.UUID, status string, reason string) error { + params := generated.UpdateAssignmentStatusParams{ + AgendaID: pgtype.UUID{Bytes: agendaID, Valid: true}, + ProfissionalID: pgtype.UUID{Bytes: professionalID, Valid: true}, + Status: pgtype.Text{String: status, Valid: true}, + MotivoRejeicao: pgtype.Text{String: reason, Valid: reason != ""}, + } + _, err := s.queries.UpdateAssignmentStatus(ctx, params) + return err +} diff --git a/backend/internal/db/generated/agenda.sql.go b/backend/internal/db/generated/agenda.sql.go index 6295176..db6e7c7 100644 --- a/backend/internal/db/generated/agenda.sql.go +++ b/backend/internal/db/generated/agenda.sql.go @@ -11,6 +11,22 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const assignProfessional = `-- name: AssignProfessional :exec +INSERT INTO agenda_profissionais (agenda_id, profissional_id) +VALUES ($1, $2) +ON CONFLICT (agenda_id, profissional_id) DO NOTHING +` + +type AssignProfessionalParams struct { + AgendaID pgtype.UUID `json:"agenda_id"` + ProfissionalID pgtype.UUID `json:"profissional_id"` +} + +func (q *Queries) AssignProfessional(ctx context.Context, arg AssignProfessionalParams) error { + _, err := q.db.Exec(ctx, assignProfessional, arg.AgendaID, arg.ProfissionalID) + return err +} + const createAgenda = `-- name: CreateAgenda :one INSERT INTO agenda ( fot_id, @@ -39,7 +55,7 @@ INSERT INTO agenda ( user_id ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24 -) 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 +) 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, status ` type CreateAgendaParams struct { @@ -125,6 +141,7 @@ func (q *Queries) CreateAgenda(ctx context.Context, arg CreateAgendaParams) (Age &i.PreVenda, &i.CriadoEm, &i.AtualizadoEm, + &i.Status, ) return i, err } @@ -140,7 +157,7 @@ func (q *Queries) DeleteAgenda(ctx context.Context, id pgtype.UUID) error { } const getAgenda = `-- name: GetAgenda :one -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 +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, status FROM agenda WHERE id = $1 LIMIT 1 ` @@ -175,20 +192,122 @@ func (q *Queries) GetAgenda(ctx context.Context, id pgtype.UUID) (Agenda, error) &i.PreVenda, &i.CriadoEm, &i.AtualizadoEm, + &i.Status, ) return i, err } +const getAgendaProfessionals = `-- name: GetAgendaProfessionals :many +SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.criado_em, p.atualizado_em, f.nome as funcao_nome, u.email +FROM cadastro_profissionais p +JOIN agenda_profissionais ap ON p.id = ap.profissional_id +LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id +LEFT JOIN usuarios u ON p.usuario_id = u.id +WHERE ap.agenda_id = $1 +` + +type GetAgendaProfessionalsRow struct { + ID pgtype.UUID `json:"id"` + UsuarioID pgtype.UUID `json:"usuario_id"` + Nome string `json:"nome"` + FuncaoProfissionalID pgtype.UUID `json:"funcao_profissional_id"` + Endereco pgtype.Text `json:"endereco"` + Cidade pgtype.Text `json:"cidade"` + Uf pgtype.Text `json:"uf"` + Whatsapp pgtype.Text `json:"whatsapp"` + CpfCnpjTitular pgtype.Text `json:"cpf_cnpj_titular"` + Banco pgtype.Text `json:"banco"` + Agencia pgtype.Text `json:"agencia"` + ContaPix pgtype.Text `json:"conta_pix"` + CarroDisponivel pgtype.Bool `json:"carro_disponivel"` + TemEstudio pgtype.Bool `json:"tem_estudio"` + QtdEstudio pgtype.Int4 `json:"qtd_estudio"` + TipoCartao pgtype.Text `json:"tipo_cartao"` + Observacao pgtype.Text `json:"observacao"` + QualTec pgtype.Int4 `json:"qual_tec"` + EducacaoSimpatia pgtype.Int4 `json:"educacao_simpatia"` + DesempenhoEvento pgtype.Int4 `json:"desempenho_evento"` + DispHorario pgtype.Int4 `json:"disp_horario"` + Media pgtype.Numeric `json:"media"` + TabelaFree pgtype.Text `json:"tabela_free"` + ExtraPorEquipamento pgtype.Bool `json:"extra_por_equipamento"` + Equipamentos pgtype.Text `json:"equipamentos"` + CriadoEm pgtype.Timestamptz `json:"criado_em"` + AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` + FuncaoNome pgtype.Text `json:"funcao_nome"` + Email pgtype.Text `json:"email"` +} + +func (q *Queries) GetAgendaProfessionals(ctx context.Context, agendaID pgtype.UUID) ([]GetAgendaProfessionalsRow, error) { + rows, err := q.db.Query(ctx, getAgendaProfessionals, agendaID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAgendaProfessionalsRow + for rows.Next() { + var i GetAgendaProfessionalsRow + if err := rows.Scan( + &i.ID, + &i.UsuarioID, + &i.Nome, + &i.FuncaoProfissionalID, + &i.Endereco, + &i.Cidade, + &i.Uf, + &i.Whatsapp, + &i.CpfCnpjTitular, + &i.Banco, + &i.Agencia, + &i.ContaPix, + &i.CarroDisponivel, + &i.TemEstudio, + &i.QtdEstudio, + &i.TipoCartao, + &i.Observacao, + &i.QualTec, + &i.EducacaoSimpatia, + &i.DesempenhoEvento, + &i.DispHorario, + &i.Media, + &i.TabelaFree, + &i.ExtraPorEquipamento, + &i.Equipamentos, + &i.CriadoEm, + &i.AtualizadoEm, + &i.FuncaoNome, + &i.Email, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listAgendas = `-- name: ListAgendas :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, + 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, a.status, 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 + te.nome as tipo_evento_nome, + COALESCE( + (SELECT json_agg(json_build_object( + 'professional_id', ap.profissional_id, + 'status', ap.status, + 'motivo_rejeicao', ap.motivo_rejeicao + )) + FROM agenda_profissionais ap + WHERE ap.agenda_id = a.id), + '[]'::json + ) as assigned_professionals FROM agenda a JOIN cadastro_fot cf ON a.fot_id = cf.id JOIN cursos c ON cf.curso_id = c.id @@ -199,40 +318,42 @@ ORDER BY a.data_evento ` type ListAgendasRow 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"` + 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"` + Status pgtype.Text `json:"status"` + 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"` + AssignedProfessionals interface{} `json:"assigned_professionals"` } func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) { @@ -272,6 +393,7 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) { &i.PreVenda, &i.CriadoEm, &i.AtualizadoEm, + &i.Status, &i.FotNumero, &i.Instituicao, &i.CursoNome, @@ -279,6 +401,7 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) { &i.AnoSemestre, &i.ObservacoesFot, &i.TipoEventoNome, + &i.AssignedProfessionals, ); err != nil { return nil, err } @@ -292,14 +415,24 @@ func (q *Queries) ListAgendas(ctx context.Context) ([]ListAgendasRow, error) { 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, + 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, a.status, 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 + te.nome as tipo_evento_nome, + COALESCE( + (SELECT json_agg(json_build_object( + 'professional_id', ap.profissional_id, + 'status', ap.status, + 'motivo_rejeicao', ap.motivo_rejeicao + )) + FROM agenda_profissionais ap + WHERE ap.agenda_id = a.id), + '[]'::json + ) as assigned_professionals FROM agenda a JOIN cadastro_fot cf ON a.fot_id = cf.id JOIN cursos c ON cf.curso_id = c.id @@ -311,40 +444,42 @@ 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"` + 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"` + Status pgtype.Text `json:"status"` + 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"` + AssignedProfessionals interface{} `json:"assigned_professionals"` } func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([]ListAgendasByUserRow, error) { @@ -384,6 +519,7 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([] &i.PreVenda, &i.CriadoEm, &i.AtualizadoEm, + &i.Status, &i.FotNumero, &i.Instituicao, &i.CursoNome, @@ -391,6 +527,7 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([] &i.AnoSemestre, &i.ObservacoesFot, &i.TipoEventoNome, + &i.AssignedProfessionals, ); err != nil { return nil, err } @@ -402,6 +539,21 @@ func (q *Queries) ListAgendasByUser(ctx context.Context, userID pgtype.UUID) ([] return items, nil } +const removeProfessional = `-- name: RemoveProfessional :exec +DELETE FROM agenda_profissionais +WHERE agenda_id = $1 AND profissional_id = $2 +` + +type RemoveProfessionalParams struct { + AgendaID pgtype.UUID `json:"agenda_id"` + ProfissionalID pgtype.UUID `json:"profissional_id"` +} + +func (q *Queries) RemoveProfessional(ctx context.Context, arg RemoveProfessionalParams) error { + _, err := q.db.Exec(ctx, removeProfessional, arg.AgendaID, arg.ProfissionalID) + return err +} + const updateAgenda = `-- name: UpdateAgenda :one UPDATE agenda SET @@ -430,7 +582,7 @@ SET pre_venda = $24, atualizado_em = NOW() WHERE id = $1 -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 +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, status ` type UpdateAgendaParams struct { @@ -516,6 +668,88 @@ func (q *Queries) UpdateAgenda(ctx context.Context, arg UpdateAgendaParams) (Age &i.PreVenda, &i.CriadoEm, &i.AtualizadoEm, + &i.Status, + ) + return i, err +} + +const updateAgendaStatus = `-- name: UpdateAgendaStatus :one +UPDATE agenda +SET status = $2, atualizado_em = NOW() +WHERE id = $1 +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, status +` + +type UpdateAgendaStatusParams struct { + ID pgtype.UUID `json:"id"` + Status pgtype.Text `json:"status"` +} + +func (q *Queries) UpdateAgendaStatus(ctx context.Context, arg UpdateAgendaStatusParams) (Agenda, error) { + row := q.db.QueryRow(ctx, updateAgendaStatus, arg.ID, arg.Status) + var i Agenda + err := row.Scan( + &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.Status, + ) + return i, err +} + +const updateAssignmentStatus = `-- name: UpdateAssignmentStatus :one +UPDATE agenda_profissionais +SET status = $3, motivo_rejeicao = $4 +WHERE agenda_id = $1 AND profissional_id = $2 +RETURNING id, agenda_id, profissional_id, status, motivo_rejeicao, criado_em +` + +type UpdateAssignmentStatusParams struct { + AgendaID pgtype.UUID `json:"agenda_id"` + ProfissionalID pgtype.UUID `json:"profissional_id"` + Status pgtype.Text `json:"status"` + MotivoRejeicao pgtype.Text `json:"motivo_rejeicao"` +} + +func (q *Queries) UpdateAssignmentStatus(ctx context.Context, arg UpdateAssignmentStatusParams) (AgendaProfissionai, error) { + row := q.db.QueryRow(ctx, updateAssignmentStatus, + arg.AgendaID, + arg.ProfissionalID, + arg.Status, + arg.MotivoRejeicao, + ) + var i AgendaProfissionai + err := row.Scan( + &i.ID, + &i.AgendaID, + &i.ProfissionalID, + &i.Status, + &i.MotivoRejeicao, + &i.CriadoEm, ) return i, err } diff --git a/backend/internal/db/generated/models.go b/backend/internal/db/generated/models.go index 558b88f..fc60f31 100644 --- a/backend/internal/db/generated/models.go +++ b/backend/internal/db/generated/models.go @@ -36,6 +36,16 @@ type Agenda struct { PreVenda pgtype.Bool `json:"pre_venda"` CriadoEm pgtype.Timestamptz `json:"criado_em"` AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` + Status pgtype.Text `json:"status"` +} + +type AgendaProfissionai struct { + ID pgtype.UUID `json:"id"` + AgendaID pgtype.UUID `json:"agenda_id"` + ProfissionalID pgtype.UUID `json:"profissional_id"` + Status pgtype.Text `json:"status"` + MotivoRejeicao pgtype.Text `json:"motivo_rejeicao"` + CriadoEm pgtype.Timestamptz `json:"criado_em"` } type AnosFormatura struct { diff --git a/backend/internal/db/generated/profissionais.sql.go b/backend/internal/db/generated/profissionais.sql.go index 92631f9..0a38314 100644 --- a/backend/internal/db/generated/profissionais.sql.go +++ b/backend/internal/db/generated/profissionais.sql.go @@ -270,9 +270,10 @@ func (q *Queries) GetProfissionalByUsuarioID(ctx context.Context, usuarioID pgty } const listProfissionais = `-- name: ListProfissionais :many -SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.criado_em, p.atualizado_em, f.nome as funcao_nome +SELECT p.id, p.usuario_id, p.nome, p.funcao_profissional_id, p.endereco, p.cidade, p.uf, p.whatsapp, p.cpf_cnpj_titular, p.banco, p.agencia, p.conta_pix, p.carro_disponivel, p.tem_estudio, p.qtd_estudio, p.tipo_cartao, p.observacao, p.qual_tec, p.educacao_simpatia, p.desempenho_evento, p.disp_horario, p.media, p.tabela_free, p.extra_por_equipamento, p.equipamentos, p.criado_em, p.atualizado_em, f.nome as funcao_nome, u.email FROM cadastro_profissionais p LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id +LEFT JOIN usuarios u ON p.usuario_id = u.id ORDER BY p.nome ` @@ -305,6 +306,7 @@ type ListProfissionaisRow struct { CriadoEm pgtype.Timestamptz `json:"criado_em"` AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"` FuncaoNome pgtype.Text `json:"funcao_nome"` + Email pgtype.Text `json:"email"` } func (q *Queries) ListProfissionais(ctx context.Context) ([]ListProfissionaisRow, error) { @@ -345,6 +347,7 @@ func (q *Queries) ListProfissionais(ctx context.Context) ([]ListProfissionaisRow &i.CriadoEm, &i.AtualizadoEm, &i.FuncaoNome, + &i.Email, ); err != nil { return nil, err } diff --git a/backend/internal/db/queries/agenda.sql b/backend/internal/db/queries/agenda.sql index a5c54e5..e2a525e 100644 --- a/backend/internal/db/queries/agenda.sql +++ b/backend/internal/db/queries/agenda.sql @@ -41,7 +41,17 @@ SELECT e.nome as empresa_nome, af.ano_semestre, cf.observacoes as observacoes_fot, - te.nome as tipo_evento_nome + te.nome as tipo_evento_nome, + COALESCE( + (SELECT json_agg(json_build_object( + 'professional_id', ap.profissional_id, + 'status', ap.status, + 'motivo_rejeicao', ap.motivo_rejeicao + )) + FROM agenda_profissionais ap + WHERE ap.agenda_id = a.id), + '[]'::json + ) as assigned_professionals FROM agenda a JOIN cadastro_fot cf ON a.fot_id = cf.id JOIN cursos c ON cf.curso_id = c.id @@ -59,7 +69,17 @@ SELECT e.nome as empresa_nome, af.ano_semestre, cf.observacoes as observacoes_fot, - te.nome as tipo_evento_nome + te.nome as tipo_evento_nome, + COALESCE( + (SELECT json_agg(json_build_object( + 'professional_id', ap.profissional_id, + 'status', ap.status, + 'motivo_rejeicao', ap.motivo_rejeicao + )) + FROM agenda_profissionais ap + WHERE ap.agenda_id = a.id), + '[]'::json + ) as assigned_professionals FROM agenda a JOIN cadastro_fot cf ON a.fot_id = cf.id JOIN cursos c ON cf.curso_id = c.id @@ -102,3 +122,32 @@ RETURNING *; -- name: DeleteAgenda :exec DELETE FROM agenda WHERE id = $1; + +-- name: AssignProfessional :exec +INSERT INTO agenda_profissionais (agenda_id, profissional_id) +VALUES ($1, $2) +ON CONFLICT (agenda_id, profissional_id) DO NOTHING; + +-- name: RemoveProfessional :exec +DELETE FROM agenda_profissionais +WHERE agenda_id = $1 AND profissional_id = $2; + +-- name: GetAgendaProfessionals :many +SELECT p.*, f.nome as funcao_nome, u.email +FROM cadastro_profissionais p +JOIN agenda_profissionais ap ON p.id = ap.profissional_id +LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id +LEFT JOIN usuarios u ON p.usuario_id = u.id +WHERE ap.agenda_id = $1; + +-- name: UpdateAgendaStatus :one +UPDATE agenda +SET status = $2, atualizado_em = NOW() +WHERE id = $1 +RETURNING *; + +-- name: UpdateAssignmentStatus :one +UPDATE agenda_profissionais +SET status = $3, motivo_rejeicao = $4 +WHERE agenda_id = $1 AND profissional_id = $2 +RETURNING *; diff --git a/backend/internal/db/queries/profissionais.sql b/backend/internal/db/queries/profissionais.sql index d6e01bf..1130e7b 100644 --- a/backend/internal/db/queries/profissionais.sql +++ b/backend/internal/db/queries/profissionais.sql @@ -23,9 +23,10 @@ LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id WHERE p.id = $1 LIMIT 1; -- name: ListProfissionais :many -SELECT p.*, f.nome as funcao_nome +SELECT p.*, f.nome as funcao_nome, u.email FROM cadastro_profissionais p LEFT JOIN funcoes_profissionais f ON p.funcao_profissional_id = f.id +LEFT JOIN usuarios u ON p.usuario_id = u.id ORDER BY p.nome; -- name: UpdateProfissional :one diff --git a/backend/internal/db/schema.sql b/backend/internal/db/schema.sql index a57877e..a398aca 100644 --- a/backend/internal/db/schema.sql +++ b/backend/internal/db/schema.sql @@ -341,5 +341,16 @@ CREATE TABLE IF NOT EXISTS agenda ( logistica_observacoes TEXT, pre_venda BOOLEAN DEFAULT FALSE, criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), - atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW() + atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), + status VARCHAR(50) DEFAULT 'Pendente' -- Pendente, Aprovado, Arquivado +); + +CREATE TABLE IF NOT EXISTS agenda_profissionais ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + agenda_id UUID NOT NULL REFERENCES agenda(id) ON DELETE CASCADE, + profissional_id UUID NOT NULL REFERENCES cadastro_profissionais(id) ON DELETE CASCADE, + status VARCHAR(20) DEFAULT 'PENDENTE', -- PENDENTE, ACEITO, REJEITADO + motivo_rejeicao TEXT, + criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(agenda_id, profissional_id) ); diff --git a/backend/internal/profissionais/handler.go b/backend/internal/profissionais/handler.go index 9d73adf..fd6c19e 100644 --- a/backend/internal/profissionais/handler.go +++ b/backend/internal/profissionais/handler.go @@ -46,6 +46,7 @@ type ProfissionalResponse struct { TabelaFree *string `json:"tabela_free"` ExtraPorEquipamento *bool `json:"extra_por_equipamento"` Equipamentos *string `json:"equipamentos"` + Email string `json:"email"` } func toResponse(p interface{}) ProfissionalResponse { @@ -112,6 +113,7 @@ func toResponse(p interface{}) ProfissionalResponse { TabelaFree: fromPgText(v.TabelaFree), ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento), Equipamentos: fromPgText(v.Equipamentos), + Email: v.Email.String, } case generated.GetProfissionalByIDRow: return ProfissionalResponse{ diff --git a/frontend/components/EventTable.tsx b/frontend/components/EventTable.tsx index 8223f28..b872e4c 100644 --- a/frontend/components/EventTable.tsx +++ b/frontend/components/EventTable.tsx @@ -8,6 +8,8 @@ interface EventTableProps { onEventClick: (event: EventData) => void; onApprove?: (e: React.MouseEvent, eventId: string) => void; userRole: UserRole; + currentProfessionalId?: string; + onAssignmentResponse?: (e: React.MouseEvent, eventId: string, status: string, reason?: string) => void; } type SortField = @@ -26,15 +28,18 @@ export const EventTable: React.FC = ({ onEventClick, onApprove, userRole, + currentProfessionalId, + onAssignmentResponse, }) => { const canApprove = userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN; + const isPhotographer = userRole === UserRole.PHOTOGRAPHER; + const [sortField, setSortField] = useState(null); const [sortOrder, setSortOrder] = useState(null); const handleSort = (field: SortField) => { if (sortField === field) { - // Se já está ordenando por este campo, alterna a ordem if (sortOrder === "asc") { setSortOrder("desc"); } else if (sortOrder === "desc") { @@ -42,7 +47,6 @@ export const EventTable: React.FC = ({ setSortField(null); } } else { - // Novo campo, começa com ordem ascendente setSortField(field); setSortOrder("asc"); } @@ -216,80 +220,122 @@ export const EventTable: React.FC = ({ {getSortIcon("status")} - {canApprove && ( - + {(canApprove || isPhotographer) && ( + Ações )} - {sortedEvents.map((event) => ( - onEventClick(event)} - className="hover:bg-gray-50 cursor-pointer transition-colors" - > - - - {event.fot || "-"} - - - - - {formatDate(event.date)} - - - - - {event.curso || "-"} - - - - - {event.instituicao || "-"} - - - - - {event.anoFormatura || "-"} - - - - - {event.empresa || "-"} - - - - {event.type} - - - - {getStatusDisplay(event.status)} - - - {canApprove && ( - e.stopPropagation()} - > - {event.status === EventStatus.PENDING_APPROVAL && ( - - )} + {sortedEvents.map((event) => { + // Logic to find photographer assignment status + let photographerAssignment = null; + if (isPhotographer && currentProfessionalId && event.assignments) { + photographerAssignment = event.assignments.find(a => a.professionalId === currentProfessionalId); + } + + return ( + onEventClick(event)} + className="hover:bg-gray-50 cursor-pointer transition-colors" + > + + + {event.fot || "-"} + - )} - - ))} + + + {formatDate(event.date)} + + + + + {event.curso || "-"} + + + + + {event.instituicao || "-"} + + + + + {event.anoFormatura || "-"} + + + + + {event.empresa || "-"} + + + + {event.type} + + + + {getStatusDisplay(event.status)} + + + {(canApprove || isPhotographer) && ( + e.stopPropagation()} + > +
+ {canApprove && event.status === EventStatus.PENDING_APPROVAL && ( + + )} + + {isPhotographer && photographerAssignment && ( + <> + {photographerAssignment.status === "PENDENTE" && ( + <> + + + + )} + {photographerAssignment.status === "ACEITO" && ( + Aceito + )} + {photographerAssignment.status === "REJEITADO" && ( + Rejeitado + )} + + )} +
+ + )} + + ); + })} diff --git a/frontend/components/FotForm.tsx b/frontend/components/FotForm.tsx index 023c4e1..3985d85 100644 --- a/frontend/components/FotForm.tsx +++ b/frontend/components/FotForm.tsx @@ -1,16 +1,17 @@ import React, { useState, useEffect } from "react"; import { X, AlertTriangle, Save, Loader } from "lucide-react"; import { Button } from "./Button"; -import { getCompanies, getAvailableCourses, getGraduationYears, createCadastroFot } from "../services/apiService"; +import { getCompanies, getAvailableCourses, getGraduationYears, createCadastroFot, updateCadastroFot } from "../services/apiService"; interface FotFormProps { onCancel: () => void; onSubmit: (success: boolean) => void; token: string; existingFots: number[]; // List of existing FOT numbers for validation + initialData?: any; // For editing } -export const FotForm: React.FC = ({ onCancel, onSubmit, token, existingFots }) => { +export const FotForm: React.FC = ({ onCancel, onSubmit, token, existingFots, initialData }) => { const [formData, setFormData] = useState({ fot: "", empresa_id: "", @@ -24,6 +25,23 @@ export const FotForm: React.FC = ({ onCancel, onSubmit, token, exi pre_venda: false, }); + useEffect(() => { + if (initialData) { + setFormData({ + fot: initialData.fot.toString(), + empresa_id: initialData.empresa_id, + curso_id: initialData.curso_id, + ano_formatura_id: initialData.ano_formatura_id, + instituicao: initialData.instituicao || "", + cidade: initialData.cidade || "", + estado: initialData.estado || "", + observacoes: initialData.observacoes || "", + gastos_captacao: initialData.gastos_captacao ? initialData.gastos_captacao.toString() : "", + pre_venda: initialData.pre_venda || false, + }); + } + }, [initialData]); + const [companies, setCompanies] = useState([]); const [coursesList, setCoursesList] = useState([]); const [years, setYears] = useState([]); @@ -61,7 +79,17 @@ export const FotForm: React.FC = ({ onCancel, onSubmit, token, exi const val = e.target.value; setFormData({ ...formData, fot: val }); - if (val && existingFots.includes(parseInt(val))) { + if (!initialData && val && existingFots.includes(parseInt(val))) { // Check uniqueness only if new or changed (and not matching self? Logic simplied: only if not editing self, but passed list does not track who is who, assumed list of ALL. If editing, I AM in the list. OK, let's refine: existingFots should ideally exclude self if editing. We'll leave basic check but maybe warn only. Or just relax check for edit if value matches initial.) + if (initialData && parseInt(val) === initialData.fot) { + setFotError(null); + } else { + if (existingFots.includes(parseInt(val))) { + setFotError(`O FOT ${val} já existe!`); + } else { + setFotError(null); + } + } + } else if (val && existingFots.includes(parseInt(val))) { setFotError(`O FOT ${val} já existe!`); } else { setFotError(null); @@ -89,7 +117,12 @@ export const FotForm: React.FC = ({ onCancel, onSubmit, token, exi pre_venda: formData.pre_venda, }; - const result = await createCadastroFot(payload, token); + let result; + if (initialData) { + result = await updateCadastroFot(initialData.id, payload, token); + } else { + result = await createCadastroFot(payload, token); + } if (result.error) { throw new Error(result.error); @@ -98,11 +131,11 @@ export const FotForm: React.FC = ({ onCancel, onSubmit, token, exi onSubmit(true); } catch (err: any) { setError(err.message || "Erro ao salvar FOT."); - } finally { setIsSubmitting(false); } }; + if (loadingDependencies) { return (
diff --git a/frontend/contexts/DataContext.tsx b/frontend/contexts/DataContext.tsx index dd6b2ac..9709a43 100644 --- a/frontend/contexts/DataContext.tsx +++ b/frontend/contexts/DataContext.tsx @@ -1,6 +1,6 @@ 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, getProfessionals, assignProfessional as apiAssignProfessional, removeProfessional as apiRemoveProfessional, updateEventStatus as apiUpdateStatus, updateAssignmentStatus as apiUpdateAssignmentStatus } from "../services/apiService"; import { EventData, EventStatus, @@ -10,6 +10,7 @@ import { User, UserApprovalStatus, UserRole, + Professional, } from "../types"; // Initial Mock Data @@ -602,8 +603,11 @@ interface DataContextType { getActiveCoursesByInstitutionId: (institutionId: string) => Course[]; getCourseById: (id: string) => Course | undefined; registerPendingUser: (userData: { id: string; name: string; email: string; phone: string; registeredInstitution?: string }) => void; + approveUser: (userId: string) => void; rejectUser: (userId: string) => void; + professionals: Professional[]; + respondToAssignment: (eventId: string, status: string, reason?: string) => Promise; } const DataContext = createContext(undefined); @@ -613,11 +617,13 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { const { token, user } = useAuth(); // Consume Auth Context - const [events, setEvents] = useState(INITIAL_EVENTS); + const [events, setEvents] = useState([]); const [institutions, setInstitutions] = useState(INITIAL_INSTITUTIONS); const [courses, setCourses] = useState(INITIAL_COURSES); + const [pendingUsers, setPendingUsers] = useState([]); + const [professionals, setProfessionals] = useState([]); // Fetch events from API useEffect(() => { @@ -630,6 +636,7 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ // Import dynamic to avoid circular dependency if any, or just use imported service const { getAgendas } = await import("../services/apiService"); const result = await getAgendas(visibleToken); + console.log("Raw Agenda Data:", result.data); // Debug logging if (result.data) { const mappedEvents: EventData[] = result.data.map((e: any) => ({ id: e.id, @@ -637,23 +644,23 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ 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. + status: EventStatus.PENDING_APPROVAL, 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 + 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 + photographerIds: Array.isArray(e.assigned_professionals) + ? e.assigned_professionals.map((a: any) => a.professional_id) + : [], institutionId: "", // TODO attendees: e.qtd_formandos, fotId: e.fot_id, // UUID @@ -665,9 +672,18 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ anoFormatura: e.ano_semestre, empresa: e.empresa_nome, observacoes: e.observacoes_fot, - typeId: e.tipo_evento_id + typeId: e.tipo_evento_id, + assignments: Array.isArray(e.assigned_professionals) + ? e.assigned_professionals.map((a: any) => ({ + professionalId: a.professional_id, + status: a.status, + rejectionReason: a.motivo_rejeicao + })) + : [], })); setEvents(mappedEvents); + } else { + setEvents([]); } } catch (error) { console.error("Failed to fetch events", error); @@ -712,8 +728,41 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ } }; fetchUsers(); + }, []); + // Fetch professionals + useEffect(() => { + const fetchProfs = async () => { + const token = localStorage.getItem('token'); + console.log("[DEBUG] Fetching Professionals...", { token }); + if (token) { + try { + const result = await getProfessionals(token); + console.log("[DEBUG] Fetch Professionals Result:", result); + if (result.data) { + const mappedProfs: Professional[] = result.data.map((p: any) => ({ + id: p.id, + usuarioId: p.usuario_id, + name: p.nome, + email: p.email || "", + role: p.funcao_nome || "Fotógrafo", + avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(p.nome)}&background=random`, // Fallback avatar + phone: p.whatsapp, + availability: {}, // Default empty availability + })); + setProfessionals(mappedProfs); + } + } catch (error) { + console.error("Failed to fetch professionals", error); + } + } else { + console.warn("[DEBUG] No token found for fetching professionals"); + } + }; + fetchProfs(); + }, [token]); + const addEvent = async (event: EventData) => { const token = localStorage.getItem("@Photum:token"); if (!token) { @@ -770,16 +819,51 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ } }; - const updateEventStatus = (id: string, status: EventStatus) => { - setEvents((prev) => prev.map((e) => (e.id === id ? { ...e, status } : e))); + const updateEventStatus = async (id: string, status: EventStatus) => { + const token = localStorage.getItem('token'); + if (token) { + try { + await apiUpdateStatus(token, id, status); + setEvents((prev) => prev.map((e) => (e.id === id ? { ...e, status } : e))); + } catch (error) { + console.error("Failed to update status", error); + } + } else { + // Fallback + setEvents((prev) => prev.map((e) => (e.id === id ? { ...e, status } : e))); + } }; - const assignPhotographer = (eventId: string, photographerId: string) => { + const assignPhotographer = async (eventId: string, photographerId: string) => { + const token = localStorage.getItem('token'); + const event = events.find(e => e.id === eventId); + if (!event) return; + + const current = event.photographerIds || []; + const isRemoving = current.includes(photographerId); + + if (token) { + try { + if (isRemoving) { + await apiRemoveProfessional(token, eventId, photographerId); + } else { + await apiAssignProfessional(token, eventId, photographerId); + } + } catch (error) { + console.error("Failed to assign/remove professional", error); + return; // Don't update state if API fails + } + } + setEvents((prev) => prev.map((e) => { if (e.id === eventId) { const current = e.photographerIds || []; - if (!current.includes(photographerId)) { + if (current.includes(photographerId)) { + // Remove + return { ...e, photographerIds: current.filter(id => id !== photographerId) }; + } else { + // Add return { ...e, photographerIds: [...current, photographerId] }; } } @@ -796,7 +880,10 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ return events.filter((e) => e.ownerId === userId); } if (role === "PHOTOGRAPHER") { - return events.filter((e) => e.photographerIds.includes(userId)); + const professional = professionals.find((p) => p.usuarioId === userId); + if (!professional) return []; + const professionalId = professional.id; + return events.filter((e) => e.photographerIds.includes(professionalId)); } return []; }; @@ -904,6 +991,30 @@ export const DataProvider: React.FC<{ children: ReactNode }> = ({ institutions, courses, pendingUsers, + professionals, + respondToAssignment: async (eventId, status, reason) => { + const token = localStorage.getItem('token'); + if (!token || !user) return; + const professional = professionals.find(p => p.usuarioId === user.id); + if (!professional) return; + + await apiUpdateAssignmentStatus(token, eventId, professional.id, status, reason); + + // Re-fetch events to update status locally efficiently (or update local state) + // For simplicity, let's update local state + setEvents((prev) => + prev.map((e) => { + if (e.id === eventId) { + const updatedAssignments = e.assignments?.map(a => + a.professionalId === professional.id ? { ...a, status: status as any, reason } : a + ) || []; + // If it wasn't in assignments (unlikely if responding), simple update + return { ...e, assignments: updatedAssignments }; + } + return e; + }) + ); + }, addEvent, updateEventStatus, assignPhotographer, diff --git a/frontend/pages/CourseManagement.tsx b/frontend/pages/CourseManagement.tsx index 46a44c3..f43e1f9 100644 --- a/frontend/pages/CourseManagement.tsx +++ b/frontend/pages/CourseManagement.tsx @@ -2,8 +2,8 @@ import React, { useState, useEffect } from "react"; import { useAuth } from "../contexts/AuthContext"; import { UserRole } from "../types"; import { Button } from "../components/Button"; -import { getCadastroFot } from "../services/apiService"; -import { Briefcase, AlertTriangle, Plus, Edit } from "lucide-react"; +import { getCadastroFot, deleteCadastroFot } from "../services/apiService"; +import { Briefcase, AlertTriangle, Plus, Edit, Trash2, Search, Filter } from "lucide-react"; import { FotForm } from "../components/FotForm"; interface FotData { @@ -18,14 +18,24 @@ interface FotData { estado: string; gastos_captacao: number; pre_venda: boolean; + empresa_id?: string; + curso_id?: string; + ano_formatura_id?: string; } export const CourseManagement: React.FC = () => { const { user } = useAuth(); const [fotList, setFotList] = useState([]); + const [filteredList, setFilteredList] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + + // Modal State const [showForm, setShowForm] = useState(false); + const [editingFot, setEditingFot] = useState(null); + + // Filter State + const [searchTerm, setSearchTerm] = useState(""); // Verificar se è admin const isAdmin = @@ -36,6 +46,21 @@ export const CourseManagement: React.FC = () => { fetchFotData(); }, [isAdmin]); + useEffect(() => { + if (searchTerm.trim() === "") { + setFilteredList(fotList); + } else { + const lowerTerm = searchTerm.toLowerCase(); + const filtered = fotList.filter(item => + item.fot.toString().includes(lowerTerm) || + item.empresa_nome.toLowerCase().includes(lowerTerm) || + item.curso_nome.toLowerCase().includes(lowerTerm) || + item.instituicao.toLowerCase().includes(lowerTerm) + ); + setFilteredList(filtered); + } + }, [searchTerm, fotList]); + const fetchFotData = async () => { if (!isAdmin) return; @@ -61,7 +86,32 @@ export const CourseManagement: React.FC = () => { const handleFormSubmit = () => { setShowForm(false); - fetchFotData(); // Refresh list after successful creation + setEditingFot(null); + fetchFotData(); // Refresh list after successful creation/update + }; + + const handleEdit = (item: FotData) => { + setEditingFot(item); + setShowForm(true); + }; + + const handleDelete = async (id: string, fotNumber: number) => { + if (!window.confirm(`Tem certeza que deseja excluir o FOT ${fotNumber}?`)) return; + + const token = localStorage.getItem("token"); + if (!token) return; + + try { + const res = await deleteCadastroFot(id, token); + if (res.error) { + alert(res.error); + } else { + fetchFotData(); + } + } catch (err) { + console.error(err); + alert("Erro ao excluir."); + } }; // Extract existing FOT numbers for uniqueness validation @@ -85,7 +135,7 @@ export const CourseManagement: React.FC = () => {
{/* Header */}
-
+

Gestão de FOT @@ -95,7 +145,10 @@ export const CourseManagement: React.FC = () => {

+ + {/* Filters Bar */} +
+
+ + setSearchTerm(e.target.value)} + /> +
+ {/* Can add more filters here later */} +
- - {/* Form Modal */} {showForm && (
setShowForm(false)} + onCancel={() => { + setShowForm(false); + setEditingFot(null); + }} onSubmit={handleFormSubmit} token={localStorage.getItem("token") || ""} existingFots={existingFots} + initialData={editingFot} />
)} @@ -145,10 +215,7 @@ export const CourseManagement: React.FC = () => { Empresa - Cursos - - - Observações + Curso Instituição @@ -162,6 +229,9 @@ export const CourseManagement: React.FC = () => { Estado + + Observações + Gastos Captação @@ -174,7 +244,7 @@ export const CourseManagement: React.FC = () => { - {fotList.length === 0 ? ( + {filteredList.length === 0 ? ( { ) : ( - fotList.map((item) => ( + filteredList.map((item) => ( { {item.curso_nome || "-"}
- -
- {item.observacoes || "-"} -
-
{item.instituicao || "-"} @@ -234,6 +299,11 @@ export const CourseManagement: React.FC = () => { {item.estado || "-"}
+ +
+ {item.observacoes || "-"} +
+
{item.gastos_captacao @@ -255,12 +325,22 @@ export const CourseManagement: React.FC = () => { - +
+ + +
)) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index cecfc9d..ac96252 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useMemo } from "react"; -import { UserRole, EventData, EventStatus, EventType } from "../types"; +import { UserRole, EventData, EventStatus, EventType, Professional } from "../types"; import { EventTable } from "../components/EventTable"; import { EventFiltersBar, EventFilters } from "../components/EventFiltersBar"; import { EventForm } from "../components/EventForm"; @@ -27,124 +27,7 @@ interface DashboardProps { initialView?: "list" | "create"; } -interface Professional { - id: string; - name: string; - email: string; - avatar: string; - role: "Fotógrafo" | "Cinegrafista" | "Recepcionista"; - availability: { - [date: string]: boolean; - }; -} -// Mock de profissionais cadastrados -const MOCK_PHOTOGRAPHERS: Professional[] = [ - { - id: "photographer-1", - name: "Carlos Silva", - email: "carlos@photum.com", - avatar: "https://i.pravatar.cc/150?u=carlos", - role: "Fotógrafo", - availability: { - "2025-12-05": true, - "2025-12-10": true, - "2025-12-15": false, - "2025-12-20": true, - }, - }, - { - id: "photographer-2", - name: "Ana Santos", - email: "ana@photum.com", - avatar: "https://i.pravatar.cc/150?u=ana", - role: "Fotógrafo", - availability: { - "2025-12-05": true, - "2025-12-10": false, - "2025-12-15": true, - "2025-12-20": true, - }, - }, - { - id: "photographer-3", - name: "João Oliveira", - email: "joao@photum.com", - avatar: "https://i.pravatar.cc/150?u=joao", - role: "Cinegrafista", - availability: { - "2025-12-05": false, - "2025-12-10": true, - "2025-12-15": true, - "2025-12-20": false, - }, - }, - { - id: "photographer-4", - name: "Maria Costa", - email: "maria@photum.com", - avatar: "https://i.pravatar.cc/150?u=maria", - role: "Fotógrafo", - availability: { - "2025-12-05": true, - "2025-12-10": true, - "2025-12-15": true, - "2025-12-20": true, - }, - }, - { - id: "photographer-5", - name: "Pedro Alves", - email: "pedro@photum.com", - avatar: "https://i.pravatar.cc/150?u=pedro", - role: "Cinegrafista", - availability: { - "2025-12-05": false, - "2025-12-10": false, - "2025-12-15": true, - "2025-12-20": true, - }, - }, - { - id: "receptionist-1", - name: "Julia Mendes", - email: "julia@photum.com", - avatar: "https://i.pravatar.cc/150?u=julia", - role: "Recepcionista", - availability: { - "2025-12-05": true, - "2025-12-10": true, - "2025-12-15": true, - "2025-12-20": false, - }, - }, - { - id: "receptionist-2", - name: "Rafael Souza", - email: "rafael@photum.com", - avatar: "https://i.pravatar.cc/150?u=rafael", - role: "Recepcionista", - availability: { - "2025-12-05": true, - "2025-12-10": true, - "2025-12-15": false, - "2025-12-20": true, - }, - }, - { - id: "videographer-1", - name: "Lucas Ferreira", - email: "lucas@photum.com", - avatar: "https://i.pravatar.cc/150?u=lucas", - role: "Cinegrafista", - availability: { - "2025-12-05": true, - "2025-12-10": false, - "2025-12-15": true, - "2025-12-20": true, - }, - }, -]; export const Dashboard: React.FC = ({ initialView = "list", @@ -156,8 +39,10 @@ export const Dashboard: React.FC = ({ addEvent, updateEventStatus, assignPhotographer, + professionals, getInstitutionById, getActiveCoursesByInstitutionId, + respondToAssignment, } = useData(); const [view, setView] = useState<"list" | "create" | "edit" | "details">( initialView @@ -186,6 +71,11 @@ export const Dashboard: React.FC = ({ const myEvents = getEventsByRole(user.id, user.role); + const currentProfessionalId = + user.role === UserRole.PHOTOGRAPHER + ? professionals.find((p) => p.usuarioId === user.id)?.id + : undefined; + // Extract unique values for filters const { availableTypes } = useMemo(() => { const types = [...new Set(myEvents.map((e) => e.type))].sort(); @@ -246,14 +136,29 @@ export const Dashboard: React.FC = ({ } }; + // Keep selectedEvent in sync with global events state + useEffect(() => { + if (selectedEvent) { + const updated = events.find((e) => e.id === selectedEvent.id); + if (updated && updated !== selectedEvent) { + setSelectedEvent(updated); + } + } + }, [events, selectedEvent]); + const handleApprove = (e: React.MouseEvent, eventId: string) => { e.stopPropagation(); - const event = events.find((e) => e.id === eventId); - if (event) { - setSelectedEvent(event); - setView("details"); - setIsTeamModalOpen(true); - } + updateEventStatus(eventId, EventStatus.CONFIRMED); + }; + + const handleAssignmentResponse = async ( + e: React.MouseEvent, + eventId: string, + status: string, + reason?: string + ) => { + e.stopPropagation(); + await respondToAssignment(eventId, status, reason); }; const handleOpenMaps = () => { @@ -279,8 +184,6 @@ export const Dashboard: React.FC = ({ const togglePhotographer = (photographerId: string) => { if (!selectedEvent) return; assignPhotographer(selectedEvent.id, photographerId); - const updated = events.find((e) => e.id === selectedEvent.id); - if (updated) setSelectedEvent(updated); }; // --- RENDERS PER ROLE --- @@ -372,8 +275,8 @@ export const Dashboard: React.FC = ({
)} @@ -852,7 +757,7 @@ export const Dashboard: React.FC = ({ {selectedEvent.photographerIds.length > 0 ? (
{selectedEvent.photographerIds.map((id) => { - const photographer = MOCK_PHOTOGRAPHERS.find( + const photographer = professionals.find( (p) => p.id === id ); return ( @@ -952,23 +857,12 @@ export const Dashboard: React.FC = ({ - {MOCK_PHOTOGRAPHERS.filter((photographer) => { + {professionals.map((photographer) => { const isAssigned = selectedEvent.photographerIds.includes( photographer.id ); - const isAvailable = - photographer.availability[selectedEvent.date] ?? - false; - return isAvailable || isAssigned; - }).map((photographer) => { - const isAssigned = - selectedEvent.photographerIds.includes( - photographer.id - ); - const isAvailable = - photographer.availability[selectedEvent.date] ?? - false; + const isAvailable = true; return ( = ({ } disabled={!isAvailable && !isAssigned} className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors ${isAssigned - ? "bg-red-100 text-red-700 hover:bg-red-200" - : isAvailable - ? "bg-brand-gold text-white hover:bg-[#a5bd2e]" - : "bg-gray-100 text-gray-400 cursor-not-allowed" + ? "bg-red-100 text-red-700 hover:bg-red-200" + : isAvailable + ? "bg-brand-gold text-white hover:bg-[#a5bd2e]" + : "bg-gray-100 text-gray-400 cursor-not-allowed" }`} > {isAssigned ? "Remover" : "Adicionar"} @@ -1043,28 +937,22 @@ export const Dashboard: React.FC = ({ ); })} - {MOCK_PHOTOGRAPHERS.filter((p) => { - const isAssigned = - selectedEvent.photographerIds.includes(p.id); - const isAvailable = - p.availability[selectedEvent.date] ?? false; - return isAvailable || isAssigned; - }).length === 0 && ( - - -
- -

- Nenhum profissional disponível para esta data -

-

- Tente selecionar outra data ou entre em contato - com a equipe -

-
- - - )} + {professionals.length === 0 && ( + + +
+ +

+ Nenhum profissional disponível para esta data +

+

+ Tente selecionar outra data ou entre em contato + com a equipe +

+
+ + + )}
diff --git a/frontend/services/apiService.ts b/frontend/services/apiService.ts index dca49d5..4fc99c8 100644 --- a/frontend/services/apiService.ts +++ b/frontend/services/apiService.ts @@ -115,6 +115,39 @@ export async function createProfessional(data: any, token?: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/profissionais`, { + 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) { + console.error("Error fetching professionals:", error); + return { + data: null, + error: error instanceof Error ? error.message : "Erro desconhecido", + isBackendDown: true, + }; + } +} + export interface EventTypeResponse { id: string; nome: string; @@ -250,6 +283,74 @@ export async function createCadastroFot(data: any, token: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/cadastro-fot/${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, + isBackendDown: false, + }; + } catch (error) { + console.error("Error updating cadastro fot:", error); + return { + data: null, + error: error instanceof Error ? error.message : "Erro desconhecido", + isBackendDown: true, + }; + } +} + +/** + * Remove um cadastro FOT + */ +export async function deleteCadastroFot(id: string, token: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/cadastro-fot/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.error || `HTTP error! status: ${response.status}`); + } + + return { + data: null, + error: null, + isBackendDown: false, + }; + } catch (error) { + console.error("Error deleting cadastro fot:", error); + return { + data: null, + error: error instanceof Error ? error.message : "Erro desconhecido", + isBackendDown: true, + }; + } +} + /** * Busca os níveis educacionais disponíveis (EF I / EF II) */ @@ -326,6 +427,29 @@ export const getAgendas = async (token: string): Promise> => } }; +export const updateAssignmentStatus = async (token: string, eventId: string, professionalId: string, status: string, reason?: string) => { + try { + const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals/${professionalId}/status`, { + method: "PATCH", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ status, reason }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + return { error: errorData.error || "Failed to update assignment status" }; + } + const data = await response.json(); + return { data }; + } catch (error) { + console.error("API updateAssignmentStatus error:", error); + return { error: "Network error" }; + } +}; + /** @@ -427,3 +551,102 @@ export async function rejectUser(userId: string, token: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + body: JSON.stringify({ professional_id: professionalId }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return { data: undefined, error: null, isBackendDown: false }; + } catch (error) { + console.error("Error assigning professional:", error); + return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true }; + } +} + +/** + * Remove um profissional de um evento + */ +export async function removeProfessional(token: string, eventId: string, professionalId: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals/${professionalId}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return { data: undefined, error: null, isBackendDown: false }; + } catch (error) { + console.error("Error removing professional:", error); + return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true }; + } +} + +/** + * Busca profissionais de um evento + */ +export async function getEventProfessionals(token: string, eventId: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/professionals`, { + 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) { + console.error("Error fetching event professionals:", error); + return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true }; + } +} + +/** + * Atualiza o status de um evento + */ +export async function updateEventStatus(token: string, eventId: string, status: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}/api/agenda/${eventId}/status`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + body: JSON.stringify({ status }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return { data: undefined, error: null, isBackendDown: false }; + } catch (error) { + console.error("Error updating event status:", error); + return { data: null, error: error instanceof Error ? error.message : "Erro desconhecido", isBackendDown: true }; + } +} diff --git a/frontend/types.ts b/frontend/types.ts index 9f12d3d..b8d4bd5 100644 --- a/frontend/types.ts +++ b/frontend/types.ts @@ -98,6 +98,18 @@ export interface ChecklistItem { required: boolean; } +export enum AssignmentStatus { + PENDING = "PENDENTE", + ACCEPTED = "ACEITO", + REJECTED = "REJEITADO", +} + +export interface Assignment { + professionalId: string; + status: AssignmentStatus; + reason?: string; +} + export interface EventData { id: string; name: string; @@ -128,4 +140,17 @@ export interface EventData { empresa?: string; // Nome da Empresa observacoes?: string; // Observações da FOT tipoEventoNome?: string; // Nome do Tipo de Evento + + assignments?: Assignment[]; // Lista de status de atribuições +} + +export interface Professional { + id: string; + usuarioId: string; + name: string; + email: string; // Added via JOIN + role: string; // Funcao nome + avatar?: string; + phone?: string; + availability: { [date: string]: boolean }; // Mocked or fetched? For now let's keep compatibility with mock structure }