diff --git a/app/assets/javascripts/registrations.js b/app/assets/javascripts/registrations.js index 9997a251..023659d1 100644 --- a/app/assets/javascripts/registrations.js +++ b/app/assets/javascripts/registrations.js @@ -1,42 +1,33 @@ -(function(registrations) { +(function (registrations) { registrations(window.jQuery, window, document); -})(function($, window, document) { - $(function() { - $(document).ready(function() { +})(function ($, window, document) { + $(function () { + $(document).ready(function () { var programType = $( "input[type=radio][name=program_type][checked='checked']" ).val(); + if (programType) { - updateVisibleInputs(programType); + programTypeSelected(programType); } }); - $("input[type=radio][name=program_type]").change(function() { - updateVisibleInputs(this.value); + $("input[type=radio][name=program_type]").change(function () { + programTypeSelected(this.value); }); - $("#user_program_id").change(function(e) { + $("#user_program_id").change(function (e) { var programId = $(this).val(); $("#previously_selected_program").val(programId); programSelected(programId); }); - $("#user_program_location_id").change(function() { + $("#user_program_location_id").change(function () { var locationId = $(this).val(); $("#user_previous_location_id").val(locationId); }); - $("#user_acting_as").change(function(e) { - var newRole = $(this).val(); - - if (newRole == "Student") { - showStudentFields(); - } else { - hideStudentFields(); - } - }); - - $("#chzn").change(function(e) { + $("#chzn").change(function (e) { var selection = $(this).val(); if (selection) { @@ -48,109 +39,79 @@ }); }); - function updateVisibleInputs(programType) { - if (programType !== "none") { - $("#user_program_id.hideUntilActive").show(); - getProgramsOfType(programType).done(updateProgramSelection); - } else { + function programTypeSelected(programType) { + if (programType == "none") { $("#user_program_id").val(null); $("#user_program_location_id").val(null); $(".hideUntilActive").hide(); + } else { + $("#user_program_id.hideUntilActive").show(); + updateProgramOptions(programType); } - $("#program_location_fields.hideUntilActive").hide(); - updateSchoolsSection(programType); - } - function tryPreviousProgramSelection() { - var programId = $("#previously_selected_program").val(); - if (!programId) { - return; - } - if ( - $("#user_program_id").find("option[value=" + programId + "]").length > 0 - ) { - $("#user_program_id").val(programId); - programSelected(programId); - } - } + $("#program_location_fields.hideUntilActive").hide(); - function tryPreviousLocationSelection() { - var previousId = $("#user_previous_location_id").val(); - if (!previousId) { - return; - } - if ( - $("#user_program_location_id").find("option[value=" + previousId + "]") - .length > 0 - ) { - $("#user_program_location_id").val(previousId); + if (programType == "students_and_parents") { + schoolsForm.showForm(); + } else { + schoolsForm.hideForm(); } } function programSelected(id) { if (id === "") { $("#program_location_fields.hideUntilActive").hide(); - $("#school_fields.hideUntilActive").hide(); + schoolsForm.hideForm(); } else { - getNewProgramData(id).done(updateRegistrationFields); + $.ajax({ + method: "post", + url: "/ajax/programs/select_program", + data: { program_id: id }, + }).done(function (response) { + updateProgramLocationSection(response.program_locations); + }); } } - function getProgramsOfType(type) { - return $.ajax({ + function updateProgramOptions(type) { + $.ajax({ method: "post", url: "/ajax/programs/sub_programs", - data: { parent_type: type } - }); - } + data: { parent_type: type }, + }).done(function (data) { + var newOptionsArray = data.map(function (obj) { + return [obj.id, obj.program_name]; + }); + + $("#user_program_id").updateDropdown("Program", newOptionsArray); + + if (newOptionsArray.length === 1) { + $("#user_program_id option") + .filter(function () { + return ( + !this.value || + $.trim(this.value).length == 0 || + $.trim(this.text).length == 0 + ); + }) + .remove(); + } - function updateProgramSelection(data) { - var newOptionsArray = data.map(function(obj) { - return [obj.id, obj.program_name]; + tryPreviousProgramSelection(); }); - - $("#user_program_id").updateDropdown("Program", newOptionsArray); - - tryPreviousProgramSelection(); } - function getNewProgramData(programId) { - return $.ajax({ - method: "post", - url: "/ajax/programs/select_program", - data: { program_id: programId } + function updateProgramLocationSection(programLocations) { + var enabledLocations = programLocations.filter(function (obj) { + return JSON.parse(obj.enabled); }); - } - - function updateRegistrationFields(data) { - var programType = data.parent_type; - var programLocations = data.program_locations; - - updateProgramLocationSection(programLocations); - updateSchoolsSection(programType); - } - - function showStudentFields() { - $("#student-only.hideUntilActive").show(); - $("#user_student_id").attr("placeholder", "Student ID #"); - } - - function hideStudentFields() { - $("#student-only").hide(); - $("#user_student_id").attr("placeholder", "Students' ID #s"); - } - function updateProgramLocationSection(programLocations) { - if (programLocations.length > 0) { + if (enabledLocations.length > 0) { $("#program_location_fields.hideUntilActive").show(); - var newOptionsArray = programLocations - .filter(function(obj) { - return JSON.parse(obj.enabled); - }) - .map(function(obj) { - return [obj.id, obj.location_name]; - }); + var newOptionsArray = enabledLocations.map(function (obj) { + return [obj.id, obj.location_name]; + }); $("#user_program_location_id").updateDropdown( "Location", @@ -162,11 +123,29 @@ } } - function updateSchoolsSection(programType) { - if (programType == "students_and_parents") { - $("#school_fields.hideUntilActive").show(); - } else { - $("#school_fields.hideUntilActive").hide(); + function tryPreviousProgramSelection() { + var programId = $("#previously_selected_program").val(); + if (!programId) { + return; + } + if ( + $("#user_program_id").find("option[value=" + programId + "]").length > 0 + ) { + $("#user_program_id").val(programId); + programSelected(programId); + } + } + + function tryPreviousLocationSelection() { + var previousId = $("#user_previous_location_id").val(); + if (!previousId) { + return; + } + if ( + $("#user_program_location_id").find("option[value=" + previousId + "]") + .length > 0 + ) { + $("#user_program_location_id").val(previousId); } } }); diff --git a/app/assets/javascripts/registrations/schools_form.js b/app/assets/javascripts/registrations/schools_form.js new file mode 100644 index 00000000..d3be6127 --- /dev/null +++ b/app/assets/javascripts/registrations/schools_form.js @@ -0,0 +1,71 @@ +var schoolsForm = (function () { + function showForm() { + $("#school_fields").show(); + $("#user_acting_as").trigger("change"); + } + + function hideForm() { + $("#school_fields").hide(); + $("#user_student_id").prop("required", false); + } + + function loadSchoolsByType(schoolType) { + $.get("/ajax/schools", { school_type: schoolType }).done(function (data) { + newOptions = data.map(function (obj) { + return [obj.id, obj.school_name]; + }); + + $("#user_school_id").updateDropdown("School", newOptions); + $("#school_fields .hideUntilActive").show(); + }); + } + + function showStudentFields() { + $("#student-only").show(); + } + + function hideStudentFields() { + $("#student-only").hide(); + } + + function makeStudentIdRequired() { + $("#user_student_id").prop("required", true); + } + + function makeStudentIdOptional() { + $("#user_student_id").prop("required", false); + } + + return { + showForm: showForm, + hideForm: hideForm, + loadSchoolsByType: loadSchoolsByType, + showStudentFields: showStudentFields, + hideStudentFields: hideStudentFields, + makeStudentIdRequired: makeStudentIdRequired, + makeStudentIdOptional: makeStudentIdOptional, + }; +})(); + +$(document).ready(function () { + $("#user_acting_as").change(function (e) { + var newRole = $(this).val(); + + if (newRole == "Student") { + schoolsForm.showStudentFields(); + schoolsForm.makeStudentIdRequired(); + } else { + schoolsForm.hideStudentFields(); + schoolsForm.makeStudentIdOptional(); + } + }); + + $("#school_type").change(function () { + var schoolType = $(this).val(); + + if (schoolType !== "") { + $("#school_type option[value='']").remove(); + schoolsForm.loadSchoolsByType(schoolType); + } + }); +}); diff --git a/app/assets/stylesheets/components/_admin_resource_list.scss b/app/assets/stylesheets/components/_admin_resource_list.scss index f1dc93a3..ba51bc2a 100644 --- a/app/assets/stylesheets/components/_admin_resource_list.scss +++ b/app/assets/stylesheets/components/_admin_resource_list.scss @@ -5,6 +5,8 @@ .resource-row { padding: 1em 0; display: flex; + align-items: center; + align-content: stretch; border-top: 1px solid; border-bottom: 1px solid; @@ -22,11 +24,11 @@ border-top: none; } - .resource-title { - flex-basis: 50%; - } + .resource-cell { + flex: 1 1 0px; - .resource-link { - flex-basis: 50%; + &:not(:first-child) { + padding: 0 1rem; + } } } diff --git a/app/assets/stylesheets/components/_new_school_form.scss b/app/assets/stylesheets/components/_new_school_form.scss new file mode 100644 index 00000000..d0e84b4a --- /dev/null +++ b/app/assets/stylesheets/components/_new_school_form.scss @@ -0,0 +1,5 @@ +.new-school-form { + select { + max-width: 200px; + } +} diff --git a/app/assets/stylesheets/mixins/_buttons.scss b/app/assets/stylesheets/mixins/_buttons.scss index 1e628e47..be23d474 100644 --- a/app/assets/stylesheets/mixins/_buttons.scss +++ b/app/assets/stylesheets/mixins/_buttons.scss @@ -18,6 +18,11 @@ letter-spacing: 1px; outline: none; + &.skinny { + min-width: 200px; + padding: 11px 20px; + } + @media (max-width: $breakpoint-tablet) { min-width: 0; } diff --git a/app/assets/stylesheets/partials/_forms.scss b/app/assets/stylesheets/partials/_forms.scss index fc343aef..a8a4f093 100644 --- a/app/assets/stylesheets/partials/_forms.scss +++ b/app/assets/stylesheets/partials/_forms.scss @@ -97,8 +97,11 @@ textarea { padding-right: 2em; } - input { - &:nth-child(1) { + input, + select { + flex: 0 1 auto; + + &:not(:last-child) { margin-right: 1em; } } diff --git a/app/controllers/admin/programs_controller.rb b/app/controllers/admin/programs_controller.rb index b1f735ae..196712bb 100644 --- a/app/controllers/admin/programs_controller.rb +++ b/app/controllers/admin/programs_controller.rb @@ -21,7 +21,7 @@ def create end def index - @programs = policy_scope(Program) + @programs = policy_scope(Program).active end def edit diff --git a/app/controllers/admin/schools_controller.rb b/app/controllers/admin/schools_controller.rb index ba941689..57129e27 100644 --- a/app/controllers/admin/schools_controller.rb +++ b/app/controllers/admin/schools_controller.rb @@ -5,7 +5,7 @@ class SchoolsController < BaseController before_action :enable_sidebar def index - @schools = policy_scope(School) + @schools = policy_scope(School).order(Arel.sql('lower(school_name)')) @new_school = current_organization.schools.new end @@ -23,6 +23,18 @@ def create end end + def update + @school = @school = School.find(params[:id]) + authorize @school + + respond_to do |format| + format.html { redirect_to action: 'index' } + format.js do + @school.update(school_params) + end + end + end + def toggle @school = School.find(params[:school_id]) authorize @school, :update? @@ -41,7 +53,7 @@ def toggle private def school_params - params.require(:school).permit(:school_name) + params.require(:school).permit(:school_name, :school_type) end end diff --git a/app/controllers/ajax/programs_controller.rb b/app/controllers/ajax/programs_controller.rb index f0a85137..1ed920a0 100644 --- a/app/controllers/ajax/programs_controller.rb +++ b/app/controllers/ajax/programs_controller.rb @@ -5,7 +5,7 @@ class ProgramsController < ApplicationController def sub_programs skip_authorization - @programs = Program.where(parent_type: Program.parent_types[params[:parent_type].to_sym]) + @programs = current_organization.programs.where(parent_type: Program.parent_types[params[:parent_type].to_sym]).active respond_to do |format| format.json { render json: @programs.to_json, status: :ok } diff --git a/app/controllers/ajax/schools_controller.rb b/app/controllers/ajax/schools_controller.rb new file mode 100644 index 00000000..c42b8b26 --- /dev/null +++ b/app/controllers/ajax/schools_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Ajax + class SchoolsController < ApplicationController + skip_after_action :verify_policy_scoped, only: :index + + def index + @schools = current_organization.schools.where(school_type: params[:school_type]).enabled + render json: @schools + end + + end +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 4fcdf20f..ab83730c 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -5,6 +5,7 @@ class RegistrationsController < Devise::RegistrationsController def create skip_authorization + @user = User.new(sign_up_params) @library_card_login = current_organization.library_card_login? if verify_recaptcha(model: @user) @@ -25,14 +26,6 @@ def create_organization_user_entry private def sign_up_params - - if params[:user]['date_of_birth(1i)'].present? - month = params[:user]['date_of_birth(2i)'].to_i - day = params[:user]['date_of_birth(3i)'].to_i - year = params[:user]['date_of_birth(1i)'].to_i - params[:user][:date_of_birth] = Date.new(year, month, day) - end - if current_organization.accepts_custom_branches? params[:user][:profile_attributes] = convert_branch_params(params[:user][:profile_attributes]) end @@ -78,7 +71,7 @@ def programs_params end if params[:user][:acting_as] == 'Student' - allowed_programs_params << %i[date_of_birth grade school_id] + allowed_programs_params << %i[grade school_id] end end diff --git a/app/helpers/admin/schools_helper.rb b/app/helpers/admin/schools_helper.rb new file mode 100644 index 00000000..24ed5fdf --- /dev/null +++ b/app/helpers/admin/schools_helper.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Admin + module SchoolsHelper + def school_type_options + School.school_types.keys.map { |type| [type.titleize, type] } + end + end +end diff --git a/app/models/organization.rb b/app/models/organization.rb index 48ea1f16..1c7de0f9 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -54,7 +54,7 @@ def admin_user_emails end def student_programs? - programs.map(&:parent_type).any? { |p| p.to_sym == :students_and_parents } + programs.where(parent_type: :students_and_parents).present? end def authentication_key_field diff --git a/app/models/program.rb b/app/models/program.rb index 5af88f8c..85064019 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -16,5 +16,6 @@ class Program < ApplicationRecord accepts_nested_attributes_for :program_locations + scope :active, -> { where(active: true) } scope :for_subdomain, ->(subdomain) { joins(:organization).where('organizations.subdomain = ?', subdomain) } end diff --git a/app/models/school.rb b/app/models/school.rb index 32eeeb53..570e94fc 100644 --- a/app/models/school.rb +++ b/app/models/school.rb @@ -6,5 +6,7 @@ class School < ApplicationRecord validates :school_name, presence: true + enum school_type: { elementary: 0, middle: 1, high: 2, charter: 3, specialty: 4 } + scope :enabled, -> { where(enabled: true) } end diff --git a/app/services/completed_courses_exporter.rb b/app/services/completed_courses_exporter.rb index e7d627ee..cac1b1cf 100644 --- a/app/services/completed_courses_exporter.rb +++ b/app/services/completed_courses_exporter.rb @@ -12,7 +12,8 @@ def initialize(org) def to_csv users = User.includes(:roles).where(organization_id: @org).order(:email, :library_card_number) CSV.generate do |csv| - csv << [User.human_attribute_name(@primary_id_field), 'Program Name', 'Course', 'Course Completed At', 'Branch'] + csv << column_headers + users.each do |user| next unless user.reportable_role?(@org) @@ -20,11 +21,24 @@ def to_csv next unless cp.complete? program_name = user.program.present? ? user.program.program_name : '' - values = [user.send(@primary_id_field), program_name, cp.course.title, cp.completed_at.strftime('%m-%d-%Y'), user.profile.library_location.try(:name)] + values = [user.send(@primary_id_field), program_name, cp.course.title, cp.completed_at.strftime('%m-%d-%Y'), user.profile.library_location&.name] + values.concat([user.school&.school_type&.titleize, user.school&.school_name]) if school_program_org? csv.add_row values end end end end + private + + def column_headers + headers = [User.human_attribute_name(@primary_id_field), 'Program Name', 'Course', 'Course Completed At', 'Branch'] + headers.concat(['School Type', 'School Name']) if school_program_org? + headers + end + + def school_program_org? + @school_program_org ||= @org.student_programs? + end + end diff --git a/app/services/registration_exporter.rb b/app/services/registration_exporter.rb index 71b9317e..78ada0e1 100644 --- a/app/services/registration_exporter.rb +++ b/app/services/registration_exporter.rb @@ -20,8 +20,9 @@ def to_csv program_name = user.program.present? ? user.program.program_name : '' values = [user.send(@primary_id_field), program_name, user.created_at] - values.concat([user.library_location_name, user.library_location_zipcode]) - csv.add_row values.compact + values.concat([user.library_location_name, user.library_location_zipcode]) if @org.branches? + values.concat([user.school&.school_type&.titleize, user.school&.school_name, user.student_id]) if school_programs? + csv.add_row values end end end @@ -31,7 +32,12 @@ def to_csv def column_headers headers = [User.human_attribute_name(@primary_id_field), 'Program Name', 'Registration Date'] headers.concat(['Branch Name', 'Zip']) if @org.branches? - headers.compact + headers.concat(['School Type', 'School Name', 'Student ID(s)']) if school_programs? + headers + end + + def school_programs? + @school_programs ||= @org.student_programs? end end diff --git a/app/services/unfinished_courses_exporter.rb b/app/services/unfinished_courses_exporter.rb index 2da302a0..ac788c8e 100644 --- a/app/services/unfinished_courses_exporter.rb +++ b/app/services/unfinished_courses_exporter.rb @@ -12,7 +12,7 @@ def initialize(org) def to_csv users = User.includes(:roles).where(organization_id: @org).order(:email, :library_card_number) CSV.generate do |csv| - csv << [User.human_attribute_name(@primary_id_field), 'Program Name', 'Course', 'Course Started At'] + csv << column_headers users.each do |user| next unless user.reportable_role?(@org) @@ -20,11 +20,24 @@ def to_csv next if cp.complete? program_name = user.program.present? ? user.program.program_name : '' - values = [user.send(@primary_id_field), program_name, cp.course.title, cp.created_at.strftime('%m-%d-%Y')] + values = [user.send(@primary_id_field), program_name, cp.course.title, cp.created_at.strftime('%m-%d-%Y'), user.profile.library_location&.name] + values.concat([user.school&.school_type&.titleize, user.school&.school_name]) if school_program_org? csv.add_row values end end end end + private + + def column_headers + headers = [User.human_attribute_name(@primary_id_field), 'Program Name', 'Course', 'Course Started At', 'Branch'] + headers.concat(['School Type', 'School Name']) if school_program_org? + headers + end + + def school_program_org? + @school_program_org ||= @org.student_programs? + end + end diff --git a/app/views/admin/categories/_category_row.html.erb b/app/views/admin/categories/_category_row.html.erb index dfcefbd5..acb77c47 100644 --- a/app/views/admin/categories/_category_row.html.erb +++ b/app/views/admin/categories/_category_row.html.erb @@ -1,8 +1,8 @@