diff --git a/app/controllers/form_submissions_controller.rb b/app/controllers/form_submissions_controller.rb index 1f4b8b3028..67e180197e 100644 --- a/app/controllers/form_submissions_controller.rb +++ b/app/controllers/form_submissions_controller.rb @@ -1,4 +1,14 @@ class FormSubmissionsController < ApplicationController + def index + authorize! FormSubmission + submissions = FormSubmission.includes(:form, :event, :person) + if params[:person_id].present? + submissions = submissions.where(person_id: params[:person_id]) + @person = Person.find_by(id: params[:person_id]) + end + @form_submissions = submissions.order(created_at: :desc) + end + def show @form_submission = FormSubmission.find(params[:id]) authorize! @form_submission diff --git a/app/controllers/grants_controller.rb b/app/controllers/grants_controller.rb index f45627106e..7c0376929a 100644 --- a/app/controllers/grants_controller.rb +++ b/app/controllers/grants_controller.rb @@ -4,10 +4,14 @@ class GrantsController < ApplicationController def index authorize! - @grants = authorized_scope(Grant.all) - .includes(:donor, scholarships: { allocation: :allocatable }) - .by_deadline - .page(params[:page]) + grants = authorized_scope(Grant.all) + .includes(:donor, scholarships: { allocation: :allocatable }) + .by_deadline + if params[:donor_id].present? && Grant::DONOR_TYPES.include?(params[:donor_type]) + grants = grants.where(donor_id: params[:donor_id], donor_type: params[:donor_type]) + @donor = grants.first&.donor + end + @grants = grants.page(params[:page]) track_index_intent(Grant, @grants, params) end diff --git a/app/controllers/scholarships_controller.rb b/app/controllers/scholarships_controller.rb index 2ffae041a9..8424563c09 100644 --- a/app/controllers/scholarships_controller.rb +++ b/app/controllers/scholarships_controller.rb @@ -13,6 +13,10 @@ def index { grant: :donor }, { recipient: [ { affiliations: { organization: :addresses } }, { event_registrations: :event } ] } ) + if params[:recipient_id].present? + scholarships = scholarships.where(recipient_id: params[:recipient_id]) + @recipient = Person.find_by(id: params[:recipient_id]) + end @funder_groups = ScholarshipsGrouping.new(scholarships).funder_groups @scholarships_count = scholarships.size end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5d84242875..666c4f6ace 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -256,7 +256,11 @@ def form_field_option_source(field) people: "fa-user", organizations: "fa-building", workshops: "fa-chalkboard-user", - resources: "fa-book" + resources: "fa-book", + scholarships: "fa-graduation-cap", + notifications: "fa-bell", + grants: "fa-hand-holding-dollar", + form_submissions: "fa-file-signature" }.freeze # Themed card-style link to a filtered index. The collection drives the diff --git a/app/policies/form_submission_policy.rb b/app/policies/form_submission_policy.rb index 4add36ee46..7dccbbcba0 100644 --- a/app/policies/form_submission_policy.rb +++ b/app/policies/form_submission_policy.rb @@ -1,5 +1,9 @@ class FormSubmissionPolicy < ApplicationPolicy # See https://actionpolicy.evilmartians.io/#/writing_policies + def index? + admin? + end + def show? admin? end diff --git a/app/views/form_submissions/index.html.erb b/app/views/form_submissions/index.html.erb new file mode 100644 index 0000000000..93f6ce5144 --- /dev/null +++ b/app/views/form_submissions/index.html.erb @@ -0,0 +1,68 @@ +<% content_for(:page_bg_class, "admin-only bg-blue-100") %> + +
+
+ <% if @person %> +
+ <%= link_to edit_person_path(@person), class: "inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-700" do %> + Edit <%= @person.name %> + <% end %> +
+ <% end %> + +
+ +
+

+ Form submissions + <%= @form_submissions.size %> +

+

+ <% if @person %> + Forms submitted by <%= @person.name %>. + <% else %> + Forms submitted across the site. + <% end %> +

+
+
+ + <% if @form_submissions.any? %> +
+ + + + + + + <% unless @person %><% end %> + + + + + + <% @form_submissions.each do |submission| %> + + + + + <% unless @person %><% end %> + + + + <% end %> + +
FormRoleEventPersonSubmitted
<%= submission.form&.display_name %><%= submission.role&.humanize %><%= submission.resolved_event&.name %><%= submission.person&.name %><%= submission.created_at.to_fs(:long) %> + <%= link_to "View", form_submission_path(submission), class: "text-blue-600 hover:underline" %> +
+
+ <% else %> +
+ +

No form submissions found.

+
+ <% end %> +
+
diff --git a/app/views/grants/index.html.erb b/app/views/grants/index.html.erb index 8a7f6e86d0..502b4d9d19 100644 --- a/app/views/grants/index.html.erb +++ b/app/views/grants/index.html.erb @@ -38,6 +38,12 @@ + <% if @donor %> +
+ Filtered to <%= @donor.try(:name) || @donor.try(:full_name) %> + <%= link_to "Clear", grants_path, class: "text-blue-600 hover:underline" %> +
+ <% end %> <% if @grants.any? %>
diff --git a/app/views/people/_associated_records.html.erb b/app/views/people/_associated_records.html.erb new file mode 100644 index 0000000000..b80dfb1406 --- /dev/null +++ b/app/views/people/_associated_records.html.erb @@ -0,0 +1,11 @@ +<% return unless person.persisted? %> +<% email = person.preferred_email %> +<% notifications = email.present? ? Notification.email(email) : Notification.none %> + diff --git a/app/views/people/_form.html.erb b/app/views/people/_form.html.erb index 4ea491e6c5..845493f403 100644 --- a/app/views/people/_form.html.erb +++ b/app/views/people/_form.html.erb @@ -479,6 +479,18 @@ + <% if f.object.persisted? %> + <% person = f.object.respond_to?(:object) ? f.object.object : f.object %> +
+
+ Associated records +
+
+ <%= render "associated_records", person: person %> +
+
+ <% end %> + <% if f.object.persisted? %> <% if allowed_to?(:manage?, Comment) %> diff --git a/app/views/scholarships/index.html.erb b/app/views/scholarships/index.html.erb index e6d0b703af..b6c3bbdde2 100644 --- a/app/views/scholarships/index.html.erb +++ b/app/views/scholarships/index.html.erb @@ -24,6 +24,12 @@ + <% if @recipient %> +
+ Filtered to <%= @recipient.name %> + <%= link_to "Clear", scholarships_path, class: "text-blue-600 hover:underline" %> +
+ <% end %> <% if @funder_groups.any? %>
<%= render partial: "funder_group", collection: @funder_groups, as: :funder_group %> diff --git a/config/routes.rb b/config/routes.rb index 529c0cc97e..da8072d71e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -126,7 +126,7 @@ patch :update_sections end end - resources :form_submissions, only: [ :show ] + resources :form_submissions, only: [ :index, :show ] resources :grants resources :scholarships, only: [ :index, :new, :create, :show, :edit, :update, :destroy ] do member { patch :toggle_tasks } diff --git a/spec/requests/form_submissions_spec.rb b/spec/requests/form_submissions_spec.rb index 3aeea10efd..73777cad97 100644 --- a/spec/requests/form_submissions_spec.rb +++ b/spec/requests/form_submissions_spec.rb @@ -4,6 +4,36 @@ let(:admin) { create(:user, :admin) } let(:submission) { create(:form_submission) } + describe "GET /form_submissions" do + context "as an admin" do + before { sign_in admin } + + it "lists a person's submissions and links each to its detail page" do + person = create(:person, first_name: "Priya", last_name: "Patel") + other = create(:person) + mine = create(:form_submission, person: person) + theirs = create(:form_submission, person: other) + + get form_submissions_path(person_id: person.id) + + expect(response).to have_http_status(:ok) + expect(response.body).to include("Priya Patel") + expect(response.body).to include(form_submission_path(mine)) + expect(response.body).not_to include(form_submission_path(theirs)) + end + end + + context "as a non-admin" do + before { sign_in create(:user) } + + it "redirects away" do + get form_submissions_path + + expect(response).to redirect_to(root_path) + end + end + end + describe "GET /form_submissions/:id" do context "as an admin" do before { sign_in admin } diff --git a/spec/requests/grants_spec.rb b/spec/requests/grants_spec.rb index 6761390b1f..63f175b071 100644 --- a/spec/requests/grants_spec.rb +++ b/spec/requests/grants_spec.rb @@ -43,6 +43,18 @@ expect(response.body).to include(edit_scholarship_path(scholarship)) expect(response.body).to include("Scholarship") end + + it "filters to a single person donor when donor_id/donor_type are given" do + donor = create(:person, first_name: "Dana", last_name: "Donor") + create(:grant, name: "Dana Fund", donor: donor) + create(:grant, name: "Other Fund", donor: create(:organization, name: "Big Org")) + + get grants_url(donor_id: donor.id, donor_type: "Person") + + expect(response.body).to include("Filtered to") + expect(response.body).to include("Dana Fund") + expect(response.body).not_to include("Other Fund") + end end describe "GET /show" do diff --git a/spec/requests/people_associated_records_spec.rb b/spec/requests/people_associated_records_spec.rb new file mode 100644 index 0000000000..52302f3619 --- /dev/null +++ b/spec/requests/people_associated_records_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe "Person edit associated records", type: :request do + let(:admin) { create(:user, :admin) } + + before { sign_in admin } + + describe "GET /people/:id/edit" do + it "links to the person's filtered registrations, workshop logs, scholarships, grants, form submissions, and notifications" do + person = create(:person) + create(:event_registration, registrant: person) + create(:workshop_log, created_by: person.user) + create(:scholarship, recipient: person) + create(:grant, donor: person) + create(:form_submission, person: person) + create(:notification, recipient_email: person.preferred_email) + + get edit_person_path(person) + + expect(response).to have_http_status(:ok) + expect(response.body).to include("Associated records") + expect(response.body).to include(event_registrations_path(registrant_id: person.id)) + expect(response.body).to include(workshop_logs_person_path(person)) + expect(response.body).to include(scholarships_path(recipient_id: person.id)) + expect(response.body).to include(CGI.escapeHTML(grants_path(donor_id: person.id, donor_type: "Person"))) + expect(response.body).to include(form_submissions_path(person_id: person.id)) + expect(response.body).to include(notifications_path(email: person.preferred_email)) + end + end +end diff --git a/spec/requests/scholarships_spec.rb b/spec/requests/scholarships_spec.rb index 2bf600d32e..d7dbbd21d5 100644 --- a/spec/requests/scholarships_spec.rb +++ b/spec/requests/scholarships_spec.rb @@ -322,6 +322,19 @@ expect(response.body).to include(grant_path(grant, from_scholarships: true)) end + + it "filters to a single recipient when recipient_id is given" do + recipient = create(:person, first_name: "Carmen", last_name: "Gomez") + other = create(:person, first_name: "Jane", last_name: "Doe") + create(:scholarship, recipient: recipient) + create(:scholarship, recipient: other) + + get scholarships_path(recipient_id: recipient.id) + + expect(response.body).to include("Filtered to") + expect(response.body).to include("Carmen Gomez") + expect(response.body).not_to include("Jane Doe") + end end end diff --git a/spec/views/page_bg_class_alignment_spec.rb b/spec/views/page_bg_class_alignment_spec.rb index 97efbf1652..b4de782edb 100644 --- a/spec/views/page_bg_class_alignment_spec.rb +++ b/spec/views/page_bg_class_alignment_spec.rb @@ -128,6 +128,7 @@ "app/views/workshop_variation_ideas/index.html.erb" => "admin-only bg-blue-100", "app/views/grants/index.html.erb" => "admin-only bg-blue-100", "app/views/scholarships/index.html.erb" => "admin-only bg-blue-100", + "app/views/form_submissions/index.html.erb" => "admin-only bg-blue-100", # show "app/views/banners/show.html.erb" => "admin-only bg-blue-100", "app/views/categories/show.html.erb" => "admin-only bg-blue-100",