Skip to content

Commit

Permalink
Merge pull request #4766 from danielabar/4504-partner-profile-step-form
Browse files Browse the repository at this point in the history
[#4504] Add step-wise partner profile edit form
  • Loading branch information
cielf authored Nov 17, 2024
2 parents e8dd85d + 1f94522 commit 11541b1
Show file tree
Hide file tree
Showing 28 changed files with 727 additions and 10 deletions.
10 changes: 10 additions & 0 deletions app/assets/stylesheets/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,13 @@
margin-top: 40px;
}
}

.accordion-button.saving::after {
background-image: none;
content: "Saving...";
font-size: 0.875rem;
color: #005568;
transform: none;
width: 3rem;
cursor: not-allowed;
}
27 changes: 24 additions & 3 deletions app/controllers/partners/profiles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,38 @@ def show; end
def edit
@counties = County.in_category_name_order
@client_share_total = current_partner.profile.client_share_total

if Flipper.enabled?("partner_step_form")
@sections_with_errors = []
render "partners/profiles/step/edit"
else
render "edit"
end
end

def update
@counties = County.in_category_name_order
result = PartnerProfileUpdateService.new(current_partner, partner_params, profile_params).call
if result.success?
flash[:success] = "Details were successfully updated."
redirect_to partners_profile_path
if Flipper.enabled?("partner_step_form")
if params[:save_review]
redirect_to partners_profile_path
else
redirect_to edit_partners_profile_path
end
else
redirect_to partners_profile_path
end
else
flash[:error] = "There is a problem. Try again: %s" % result.error
render :edit
flash.now[:error] = "There is a problem. Try again: %s" % result.error
if Flipper.enabled?("partner_step_form")
error_keys = current_partner.profile.errors.attribute_names
@sections_with_errors = Partners::SectionErrorService.sections_with_errors(error_keys)
render "partners/profiles/step/edit"
else
render :edit
end
end
end

Expand Down
14 changes: 14 additions & 0 deletions app/helpers/partners_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ def humanize_boolean_3state(boolean)
end
end

# In step-wise editing of the partner profile, the partial name is used as the section header by default.
# This helper allows overriding the header with a custom display name if needed.
def partial_display_name(partial)
custom_names = {
'attached_documents' => 'Additional Documents'
}

custom_names[partial] || partial.humanize
end

def section_with_errors?(section, sections_with_errors = [])
sections_with_errors.include?(section)
end

def partner_status_badge(partner)
if partner.status == "approved"
tag.span partner.display_status, class: %w(badge badge-pill badge-primary bg-primary float-right)
Expand Down
19 changes: 19 additions & 0 deletions app/javascript/controllers/accordion_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="accordion"
// Intercepts form submission and disables the open/close section buttons.
export default class extends Controller {
static targets = [ "form" ]

disableOpenClose(event) {
event.preventDefault();

const buttons = this.element.querySelectorAll(".accordion-button");
buttons.forEach(button => {
button.disabled = true;
button.classList.add("saving");
});

this.formTarget.requestSubmit();
}
}
14 changes: 12 additions & 2 deletions app/models/partners/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,23 @@ def client_share_is_0_or_100
# their allocation actually is
total = client_share_total
if total != 0 && total != 100
errors.add(:base, "Total client share must be 0 or 100")
if Flipper.enabled?("partner_step_form")
# need to set errors on specific fields within the form so that it can be mapped to a section
errors.add(:client_share, "Total client share must be 0 or 100")
else
errors.add(:base, "Total client share must be 0 or 100")
end
end
end

def has_at_least_one_request_setting
if !(enable_child_based_requests || enable_individual_requests || enable_quantity_based_requests)
errors.add(:base, "At least one request type must be set")
if Flipper.enabled?("partner_step_form")
# need to set errors on specific fields within the form so that it can be mapped to a section
errors.add(:enable_child_based_requests, "At least one request type must be set")
else
errors.add(:base, "At least one request type must be set")
end
end
end

Expand Down
30 changes: 30 additions & 0 deletions app/services/partners/section_error_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Partners
# SectionErrorService identifies which sections of the Partner Profile step-wise form
# should expand when validation errors occur. This helps users easily locate and fix
# fields with errors in specific sections.
#
# Usage:
# error_keys = [:website, :pick_up_name, :enable_quantity_based_requests]
# sections_with_errors = Partners::SectionErrorService.sections_with_errors(error_keys)
# # => ["media_information", "pick_up_person", "partner_settings"]
#
class SectionErrorService
# Maps form sections to the associated fields (error keys) that belong to them.
SECTION_FIELD_MAPPING = {
media_information: %i[no_social_media_presence website twitter facebook instagram],
partner_settings: %i[enable_child_based_requests enable_individual_requests enable_quantity_based_requests],
pick_up_person: %i[pick_up_email pick_up_name pick_up_phone],
area_served: %i[client_share county_id]
}

# Returns a list of unique sections that contain errors based on the given error keys.
#
# @param error_keys [Array<Symbol>] Array of attribute keys representing the fields with errors.
# @return [Array<String>] An array of section names containing errors.
def self.sections_with_errors(error_keys)
error_keys.flat_map do |key|
SECTION_FIELD_MAPPING.find { |_section, fields| fields.include?(key) }&.first
end.compact.uniq.map(&:to_s)
end
end
end
1 change: 1 addition & 0 deletions app/views/layouts/partners/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Human Essentials</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">

<%= csrf_meta_tags %>

<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
Expand Down
24 changes: 24 additions & 0 deletions app/views/partners/profiles/step/_accordion_section.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<%# locals: (f:, partner:, section_id:, section_title:, icon_class:, partial_name:, sections_with_errors:) %>

<div class="accordion-item">
<h2 class="accordion-header">
<button
class="accordion-button <%= section_with_errors?(section_id, sections_with_errors) ? '' : 'collapsed' %>"
type="button" data-bs-toggle="collapse" data-bs-target="#<%= section_id %>"
aria-expanded="<%= section_with_errors?(section_id, sections_with_errors) %>"
aria-controls="<%= section_id %>">
<div class="d-flex justify-content-between align-items-center w-100">
<div class="fs-4">
<i class="fa <%= icon_class %> mr-2"></i>
<%= section_title %>
</div>
</div>
</button>
</h2>
<div id="<%= section_id %>"
class="accordion-collapse collapse <%= section_with_errors?(section_id, sections_with_errors) ? 'show' : '' %>">
<div class="accordion-body">
<%= render "partners/profiles/step/#{partial_name}_form" , f: f, partner: partner, profile: partner.profile %>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%= f.fields_for :profile, profile do |pf| %>
<h3><strong>Agency Distribution Information</strong></h3>

<div class="form-group">
<%= pf.input :distribution_times, label: "Distribution Times", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :new_client_times, label: "New Client Times", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :more_docs_required, label: "More Docs Required", class: "form-control" %>
</div>
<% end %>
51 changes: 51 additions & 0 deletions app/views/partners/profiles/step/_agency_information_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<div class="form-group">
<%= f.input :name, label: "Agency Name", required: true, class: "form-control" %>
</div>

<%= f.fields_for :profile, profile do |pf| %>
<div class="form-group">
<%= pf.input :agency_type, collection: Partner::AGENCY_TYPES.values, label: "Agency Type", class: "form-control", wrapper: :input_group %>
</div>

<div class="form-group">
<%= pf.input :other_agency_type, label: "Other Agency Type", class: "form-control" %>
</div>

<div class="form-group row">
<label class="control-label col-md-3">501(c)(3) IRS Determination Letter</label>
<% if profile.proof_of_partner_status.attached? %>
<div class="col-md-8">
Attached file: <%= link_to profile.proof_of_partner_status.blob['filename'], rails_blob_path(profile.proof_of_partner_status), class: "font-weight-bold" %>
<%= pf.file_field :proof_of_partner_status, class: "form-control-file" %>
</div>
<% else %>
<div class="col-md-8">
<%= pf.file_field :proof_of_partner_status, class: "form-control-file" %>
</div>
<% end %>
</div>

<div class="form-group">
<%= pf.input :agency_mission, as: :text, label: "Agency Mission", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :address1, label: "Address (line 1)", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :address2, label: "Address (line 2)", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :city, label: "City", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :state, label: "State", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :zip_code, label: "Zip Code", class: "form-control" %>
</div>
<% end %>
52 changes: 52 additions & 0 deletions app/views/partners/profiles/step/_agency_stability_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<%= f.fields_for :profile, profile do |pf| %>
<div class="form-group">
<%= pf.input :founded, label: "Year Founded", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :form_990, label: "Form 990 Filed", as: :radio_buttons, class: "form-control" %>
</div>

<% if profile.proof_of_form_990.attached? %>
<div class="form-group">
<label>Attached file: </label>
<%= link_to profile.proof_of_form_990.blob['filename'], rails_blob_path(profile.proof_of_form_990), class: "font-weight-bold" %>
</div>
<% end %>

<div class="form-group">
<%= pf.file_field :proof_of_form_990, class: "form-control-file" %>
</div>

<div class="form-group">
<%= pf.input :program_name, label: "Program Name(s)", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :program_description, label: "Program Description(s)", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :program_age, label: "Agency Age", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :evidence_based, label: "Evidence Based?", as: :radio_buttons, class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :case_management, label: "Case Management", as: :radio_buttons, class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :essentials_use, label: "How Are Essentials (e.g. diapers, period supplies) Used In Your Program?", as: :text, class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :receives_essentials_from_other, label: "Do You Receive Essentials From Other Sources?", class: "form-control" %>
</div>

<div class="form-group">
<%= pf.input :currently_provide_diapers, label: "Currently Providing Diapers?", as: :radio_buttons, class: "form-control" %>
</div>
<% end %>
26 changes: 26 additions & 0 deletions app/views/partners/profiles/step/_area_served_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<%= f.simple_fields_for :profile, profile do |pf| %>
<div data-controller="area-served">
<fieldset style="margin-bottom: 2rem">
<legend></legend>
<div id="served_areas" class="partner-served-area-fields partner-served-areas">
<%= render 'served_areas/served_area_fields', form: pf %>
</div>

<div class="partner-served_areas-total-row row">
<div class="partners-served-areas-total-prompt col-w">Total is:</div>
<span id="partners-served-areas-client-share-total" class="partners-served-areas-total-value col-2 numeric" data-area-served-target="total">
<%= @client_share_total %> %
</span>
<div id="partners-served-areas-client-share-total-warning" class="partners-served-areas-total-warning col-8" data-area-served-target="warning">
The total client share must be either 0 or 100 %.
</div>
</div>

<div class="row links justify-content-end">
<%= add_element_button "Add Another County", container_selector: "#served_areas", id: "__add_partner_served_area" do %>
<%= render 'served_areas/served_area_fields', form: pf, object: Partners::ServedArea.new %>
<% end %>
</div>
</fieldset>
</div>
<% end %>
15 changes: 15 additions & 0 deletions app/views/partners/profiles/step/_attached_documents_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%= f.fields_for :profile, profile do |pf| %>
<div class="form-group">
<% if profile.documents.attached? %>
<strong>Attached files:</strong>
<ul>
<% profile.documents.each do |doc| %>
<li><%= link_to doc.blob['filename'], rails_blob_path(doc), class: "font-weight-bold" %></li>
<% end %>
</ul>
<%= pf.file_field :documents, multiple: true, class: "form-control-file" %>
<% else %>
<%= pf.file_field :documents, multiple: true, class: "form-control-file" %>
<% end %>
</div>
<% end %>
26 changes: 26 additions & 0 deletions app/views/partners/profiles/step/_executive_director_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<%= f.fields_for :profile, profile do |pf| %>
<div class="form-group">
<%= pf.input :executive_director_name, label: "Executive Director Name", class: "form-control" %>
</div>
<div class="form-group">
<%= pf.input :executive_director_phone, label: "Executive Director Phone", class: "form-control" %>
</div>
<div class="form-group">
<%= pf.input :executive_director_email, label: "Executive Director Email", class: "form-control" %>
</div>

<h3 class="pt-3"><strong>Primary Contact</strong></h3>

<div class="form-group">
<%= pf.input :primary_contact_name, label: "Primary Contact Name", class: "form-control" %>
</div>
<div class="form-group">
<%= pf.input :primary_contact_phone, label: "Primary Contact Phone", class: "form-control" %>
</div>
<div class="form-group">
<%= pf.input :primary_contact_mobile, label: "Primary Contact Cell", class: "form-control" %>
</div>
<div class="form-group">
<%= pf.input :primary_contact_email, label: "Primary Contact Email", class: "form-control" %>
</div>
<% end %>
6 changes: 6 additions & 0 deletions app/views/partners/profiles/step/_form_actions.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%# locals: (f:, partner:) %>

<div class="form-group mt-4 d-flex justify-content-start mx-5">
<%= f.submit "Save Progress", class: 'btn btn-primary mr-2', data: { action: "click->accordion#disableOpenClose" } %>
<%= f.submit "Save and Review", name: "save_review", class: 'btn btn-success' %>
</div>
Loading

0 comments on commit 11541b1

Please sign in to comment.