Skip to content

Commit a034b87

Browse files
rubysclaude
andcommitted
Add Phase 5: Answer summary and PDF reporting
Created AnswersController with index and report actions for displaying and exporting question answers. The summary view shows all answers grouped by option and question, with links to person profiles. The PDF report provides a printable version matching the application's existing PDF styling. Added conditional navigation links: - "Answers" button on main index (only shown when questions exist) - "Question Answers" PDF link on publish page (only shown when questions exist) All 910 tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 80537af commit a034b87

File tree

6 files changed

+172
-0
lines changed

6 files changed

+172
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
class AnswersController < ApplicationController
2+
include Printable
3+
4+
# GET /answers
5+
def index
6+
load_answer_data
7+
end
8+
9+
# GET /answers/report.pdf
10+
def report
11+
load_answer_data
12+
13+
respond_to do |format|
14+
format.html
15+
format.pdf { render_as_pdf basename: "question-answers" }
16+
end
17+
end
18+
19+
private
20+
21+
def load_answer_data
22+
@event = Event.current
23+
24+
# Get all options that have questions
25+
@options_with_questions = Billable.where(type: 'Option')
26+
.joins(:questions)
27+
.distinct
28+
.ordered
29+
30+
# Build a structured data hash: option => question => [answers]
31+
@answer_data = {}
32+
33+
@options_with_questions.each do |option|
34+
@answer_data[option] = {}
35+
36+
option.questions.ordered.each do |question|
37+
# Get all answers for this question, with person and studio data
38+
answers = Answer.where(question_id: question.id)
39+
.joins(:person)
40+
.includes(person: :studio)
41+
.order('people.name')
42+
43+
@answer_data[option][question] = answers
44+
end
45+
end
46+
end
47+
end

app/views/answers/index.html.erb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<div class="w-full mx-auto">
2+
<% if notice.present? %>
3+
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
4+
<% end %>
5+
6+
<h1 class="text-center font-bold text-4xl mb-4">Question Answers Summary</h1>
7+
8+
<% if @options_with_questions.empty? %>
9+
<p class="text-center mt-8">No questions have been defined yet.</p>
10+
<p class="text-center mt-4">
11+
To add questions, edit an <%= link_to 'option', billables_path, class: "text-blue-600 underline" %>
12+
and add questions in the "Questions for this option" section.
13+
</p>
14+
<% else %>
15+
<% @answer_data.each do |option, questions_hash| %>
16+
<div class="mt-8 mb-12">
17+
<h2 class="font-bold text-2xl mb-4"><%= option.name %></h2>
18+
19+
<% questions_hash.each do |question, answers| %>
20+
<div class="mb-6">
21+
<h3 class="font-semibold text-xl mb-3"><%= question.question_text %></h3>
22+
23+
<% if answers.any? %>
24+
<table class="table-auto mx-auto w-full max-w-4xl border-collapse">
25+
<thead>
26+
<tr class="bg-gray-100">
27+
<th class="border border-gray-300 px-4 py-2 text-left">Person</th>
28+
<th class="border border-gray-300 px-4 py-2 text-left">Studio</th>
29+
<th class="border border-gray-300 px-4 py-2 text-left">Answer</th>
30+
</tr>
31+
</thead>
32+
<tbody>
33+
<% answers.each do |answer| %>
34+
<tr>
35+
<td class="border border-gray-300 px-4 py-2">
36+
<%= link_to answer.person.name, answer.person, class: "text-blue-600 hover:underline" %>
37+
</td>
38+
<td class="border border-gray-300 px-4 py-2">
39+
<%= answer.person.studio&.name || 'N/A' %>
40+
</td>
41+
<td class="border border-gray-300 px-4 py-2">
42+
<% if answer.answer_value.present? %>
43+
<%= answer.answer_value %>
44+
<% else %>
45+
<span class="text-gray-400 italic">No answer provided</span>
46+
<% end %>
47+
</td>
48+
</tr>
49+
<% end %>
50+
</tbody>
51+
</table>
52+
<% else %>
53+
<p class="text-gray-500 italic ml-4">No answers yet for this question.</p>
54+
<% end %>
55+
</div>
56+
<% end %>
57+
</div>
58+
<% end %>
59+
60+
<div class="mt-8 text-center">
61+
<%= link_to "Download PDF Report", report_answers_path(format: :pdf),
62+
class: "inline-block px-6 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium" %>
63+
</div>
64+
<% end %>
65+
</div>

app/views/answers/report.html.erb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<div class="w-full mx-auto print-page">
2+
<h1 class="text-center font-bold text-3xl mb-6">Question Answers Report</h1>
3+
<p class="text-center text-lg mb-8"><%= @event.name %></p>
4+
5+
<% if @options_with_questions.empty? %>
6+
<p class="text-center mt-8">No questions have been defined.</p>
7+
<% else %>
8+
<% @answer_data.each do |option, questions_hash| %>
9+
<div class="mt-6 mb-8 page-break-inside-avoid">
10+
<h2 class="font-bold text-2xl mb-4 border-b-2 border-gray-400 pb-2"><%= option.name %></h2>
11+
12+
<% questions_hash.each do |question, answers| %>
13+
<div class="mb-6">
14+
<h3 class="font-semibold text-lg mb-3"><%= question.question_text %></h3>
15+
16+
<% if answers.any? %>
17+
<table class="w-full border-collapse mb-4">
18+
<thead>
19+
<tr class="bg-gray-200">
20+
<th class="border border-gray-400 px-3 py-2 text-left font-semibold">Person</th>
21+
<th class="border border-gray-400 px-3 py-2 text-left font-semibold">Studio</th>
22+
<th class="border border-gray-400 px-3 py-2 text-left font-semibold">Answer</th>
23+
</tr>
24+
</thead>
25+
<tbody>
26+
<% answers.each do |answer| %>
27+
<tr>
28+
<td class="border border-gray-400 px-3 py-2"><%= answer.person.name %></td>
29+
<td class="border border-gray-400 px-3 py-2"><%= answer.person.studio&.name || 'N/A' %></td>
30+
<td class="border border-gray-400 px-3 py-2">
31+
<% if answer.answer_value.present? %>
32+
<%= answer.answer_value %>
33+
<% else %>
34+
<span class="text-gray-500 italic">No answer</span>
35+
<% end %>
36+
</td>
37+
</tr>
38+
<% end %>
39+
</tbody>
40+
</table>
41+
<% else %>
42+
<p class="text-gray-600 italic ml-4 mb-3">No answers for this question.</p>
43+
<% end %>
44+
</div>
45+
<% end %>
46+
</div>
47+
<% end %>
48+
<% end %>
49+
</div>

app/views/event/publish.html.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@
7171
<% end %>
7272
<li class=""><%= link_to "Student Scores", scores_people_path(as_pdf), class: "btn-pale-orange w-60" %>
7373
<li class=""><%= link_to "Staff", staff_people_path(as_pdf), class: "btn-pale-orange w-60" %>
74+
<% if Question.exists? %>
75+
<li class=""><%= link_to "Question Answers", report_answers_path(as_pdf), class: "btn-pale-orange w-60" %>
76+
<% end %>
7477
<li class=""><%= link_to "Certificates", people_certificates_path, class: "btn-blue w-60" %>
7578
</ul>
7679

app/views/event/root.html.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@
8888
<% else %>
8989
<%= link_to "Settings", settings_event_index_path , class: "btn-grey" %>
9090
<% end %>
91+
92+
<% if Question.exists? %>
93+
<%= link_to "Answers", answers_path, class: "btn-pale-blue" %>
94+
<% end %>
9195
</div>
9296

9397
<p class="mt-4 text-right">

config/routes.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@
261261
post 'update-age-costs', on: :collection
262262
end
263263

264+
resources :answers, only: [:index] do
265+
get 'report', on: :collection
266+
end
267+
264268
match "/password/reset", to: 'users#password_reset', via: %i(get post)
265269
match "/password/verify", to: 'users#password_verify', via: %i[get patch]
266270

0 commit comments

Comments
 (0)