Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,6 @@

gem "tailwindcss-rails", "~> 4.4"

gem "rspec-rails", "~> 8.0", groups: [:development, :test]

Check failure on line 69 in Gemfile

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 69 in Gemfile

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

gem "csv", "~> 3.3"
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ GEM
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
crass (1.0.6)
csv (3.3.5)
cucumber (10.1.1)
base64 (~> 0.2)
builder (~> 3.2)
Expand Down Expand Up @@ -439,6 +440,7 @@ DEPENDENCIES
bootsnap
brakeman
capybara
csv (~> 3.3)
cucumber-rails
database_cleaner
debug
Expand Down
21 changes: 21 additions & 0 deletions app/controllers/avaliacoes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,25 @@ def create
redirect_to gestao_envios_avaliacoes_path, alert: "Erro ao criar avaliação: #{@avaliacao.errors.full_messages.join(', ')}"
end
end

def resultados
@avaliacao = Avaliacao.find(params[:id])
# Pré-carrega dependências para evitar N+1.
# Nota: a associação 'respostas' existe no Modelo, mesmo que a tabela esteja pendente.
# Usamos array vazio como fallback por segurança se o BD falhar.
begin
@respostas = @avaliacao.respostas.includes(:aluno)
rescue ActiveRecord::StatementInvalid
@respostas = []
flash.now[:alert] = "A tabela de respostas ainda não está disponível."
end

respond_to do |format|
format.html
format.csv do
send_data CsvFormatterService.new(@avaliacao).generate,
filename: "resultados-avaliacao-#{@avaliacao.id}-#{Date.today}.csv"
end
end
end
end
1 change: 1 addition & 0 deletions app/models/avaliacao.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Avaliacao < ApplicationRecord
belongs_to :turma
belongs_to :modelo
belongs_to :professor_alvo, class_name: 'User', optional: true

Check failure on line 4 in app/models/avaliacao.rb

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
has_many :respostas
end
1 change: 1 addition & 0 deletions app/models/resposta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Resposta < ApplicationRecord
belongs_to :aluno, class_name: "User"
belongs_to :formulario
belongs_to :questao
belongs_to :avaliacao, optional: true

validates :aluno_id, presence: true
validates :formulario_id, presence: true
Expand Down
45 changes: 45 additions & 0 deletions app/services/csv_formatter_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'csv'

Check failure on line 1 in app/services/csv_formatter_service.rb

View workflow job for this annotation

GitHub Actions / lint

Style/StringLiterals: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.

class CsvFormatterService
def initialize(avaliacao)
@avaliacao = avaliacao
end

def generate
CSV.generate(headers: true) do |csv|
csv << headers

@avaliacao.respostas.includes(:aluno).group_by(&:aluno).each do |aluno, respostas|
row = [aluno.matricula, aluno.nome]

Check failure on line 13 in app/services/csv_formatter_service.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 13 in app/services/csv_formatter_service.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 14 in app/services/csv_formatter_service.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/TrailingWhitespace: Trailing whitespace detected.
# Organiza as respostas pela ordem das questões se possível, ou mapeamento simples
# Assumindo que queremos mapear questões para colunas

Check failure on line 17 in app/services/csv_formatter_service.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/TrailingWhitespace: Trailing whitespace detected.
# Para este MVP, vamos apenas despejar o conteúdo na ordem das questões encontradas
# Uma solução mais robusta ordenaria por ID da questão ou número

respostas.each do |resposta|
row << resposta.conteudo
end

csv << row
end
end
end

private

def headers
# Cabeçalhos estáticos para informações do Aluno
base_headers = ["Matrícula", "Nome"]

# Cabeçalhos dinâmicos para questões
# Identificando questões únicas respondidas ou todas as questões do modelo
# Para o MVP, vamos assumir que queremos todas as questões do modelo

questoes = @avaliacao.modelo.perguntas
question_headers = questoes.map.with_index { |q, i| "Questão #{i + 1}" }

base_headers + question_headers
end
end
6 changes: 6 additions & 0 deletions app/views/avaliacoes/gestao_envios.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@
%>
<%= f.submit "Gerar Avaliação", class: "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-150 ease-in-out cursor-pointer" %>
<% end %>

<% if (ultima_avaliacao = turma.avaliacoes.last) %>
<div class="mt-2">
<%= link_to "Ver Resultados (Última)", resultados_avaliacao_path(ultima_avaliacao), class: "text-blue-500 hover:text-blue-800 text-xs font-semibold" %>
</div>
<% end %>
</td>
</tr>
<% end %>
Expand Down
48 changes: 48 additions & 0 deletions app/views/avaliacoes/resultados.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-800">Resultados da Avaliação</h1>
<%= link_to "Voltar", gestao_envios_avaliacoes_path, class: "bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded" %>
</div>

<div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<div class="mb-4">
<p class="text-gray-700 text-sm font-bold mb-2">Turma: <span class="font-normal"><%= @avaliacao.turma.codigo %> - <%= @avaliacao.turma.nome %></span></p>
<p class="text-gray-700 text-sm font-bold mb-2">Template: <span class="font-normal"><%= @avaliacao.modelo.titulo %></span></p>
</div>

<% if @respostas.any? %>
<div class="flex justify-end mb-4">
<%= link_to "Download CSV", resultados_avaliacao_path(@avaliacao, format: :csv), class: "bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" %>
</div>

<div class="overflow-x-auto">
<table class="min-w-full leading-normal">
<thead>
<tr>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Matrícula</th>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Aluno</th>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Conteúdo</th>
</tr>
</thead>
<tbody>
<% @respostas.each do |resposta| %>
<tr>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"><%= resposta.aluno&.matricula %></td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"><%= resposta.aluno&.nome %></td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm"><%= resposta.conteudo %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4" role="alert">
<p class="font-bold">Atenção</p>
<p>Nenhuma resposta encontrada para esta avaliação.</p>
<% if flash[:alert] %>
<p class="text-xs mt-1"><%= flash[:alert] %></p>
<% end %>
</div>
<% end %>
</div>
</div>
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
collection do
get :gestao_envios
end
member do
get :resultados
end
end

resource :session
Expand Down
34 changes: 30 additions & 4 deletions db/seeds.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
# This file should ensure the existence of records required to run the application in every environment (production,
# development, test). The code here should be idempotent so that it can be executed at any point in every environment.
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
# Admin User
admin = User.find_or_create_by!(login: 'admin') do |u|
u.email_address = 'admin@camaar.unb.br'
u.nome = 'Administrador'
u.matricula = '000000000'
u.password = 'password'
u.password_confirmation = 'password'
u.eh_admin = true
end
puts "Usuário Admin garantido (ID: #{admin.id})"

Modelo.find_or_create_by!(titulo: "Template Padrão") do |m|
# Template Padrão
modelo = Modelo.find_or_create_by!(titulo: 'Template Padrão') do |m|
m.ativo = true
end
puts "Modelo 'Template Padrão' garantido (ID: #{modelo.id})"

# Perguntas do Template Padrão
perguntas_data = [
{ enunciado: 'O professor demonstrou domínio do conteúdo?', tipo: 'escala', opcoes: { min: 1, max: 5 } },
{ enunciado: 'O plano de ensino foi seguido?', tipo: 'escala', opcoes: { min: 1, max: 5 } },
{ enunciado: 'Como você avalia a didática do professor?', tipo: 'escala', opcoes: { min: 1, max: 5 } },
{ enunciado: 'Pontos positivos:', tipo: 'texto', opcoes: {} },
{ enunciado: 'Pontos a melhorar:', tipo: 'texto', opcoes: {} }
]

perguntas_data.each do |p_data|
Pergunta.find_or_create_by!(modelo: modelo, enunciado: p_data[:enunciado]) do |p|
p.tipo = p_data[:tipo]
p.opcoes = p_data[:opcoes]
end
end
puts "#{modelo.perguntas.count} perguntas garantidas para o Template Padrão."
15 changes: 0 additions & 15 deletions spec/helpers/avaliacoes_helper_spec.rb

This file was deleted.

46 changes: 46 additions & 0 deletions spec/services/csv_formatter_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'rails_helper'

RSpec.describe CsvFormatterService do
describe '#generate' do
let(:modelo) { double('Modelo', perguntas: [
double('Pergunta', enunciado: 'Q1'),
double('Pergunta', enunciado: 'Q2')
])}

let(:avaliacao) { double('Avaliacao', id: 1, modelo: modelo) }

let(:aluno1) { double('User', matricula: '123', nome: 'Alice') }
let(:aluno2) { double('User', matricula: '456', nome: 'Bob') }

let(:resposta_a1_q1) { double('Resposta', aluno: aluno1, conteudo: 'Ans 1A') }
let(:resposta_a1_q2) { double('Resposta', aluno: aluno1, conteudo: 'Ans 1B') }
let(:resposta_a2_q1) { double('Resposta', aluno: aluno2, conteudo: 'Ans 2A') }

# Estrutura agrupada por aluno simulando resultado de query ActiveRecord
let(:grouped_responses) do
{
aluno1 => [resposta_a1_q1, resposta_a1_q2],
aluno2 => [resposta_a2_q1]
}
end

before do
# Mock da cadeia: avaliacao.respostas.includes.group_by
allow(avaliacao).to receive_message_chain(:respostas, :includes, :group_by).and_return(grouped_responses)
end

it 'gera uma string CSV válida com cabeçalhos e linhas' do
csv_string = described_class.new(avaliacao).generate
rows = csv_string.split("\n")

# Cabeçalhos: Matrícula, Nome, Questão 1, Questão 2
expect(rows[0]).to include("Matrícula,Nome,Questão 1,Questão 2")

# Linha 1: Respostas da Alice
expect(rows[1]).to include("123,Alice,Ans 1A,Ans 1B")

# Linha 2: Respostas do Bob
expect(rows[2]).to include("456,Bob,Ans 2A")
end
end
end
5 changes: 0 additions & 5 deletions spec/views/avaliacoes/create.html.tailwindcss_spec.rb

This file was deleted.

5 changes: 0 additions & 5 deletions spec/views/avaliacoes/gestao_envios.html.tailwindcss_spec.rb

This file was deleted.

5 changes: 0 additions & 5 deletions spec/views/avaliacoes/index.html.tailwindcss_spec.rb

This file was deleted.

Loading