diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index 422484a733..62e164c872 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -1,4 +1,6 @@ class ImportsController < ApplicationController + require "csv" + include ActionView::Helpers::UrlHelper after_action :verify_authorized @@ -6,12 +8,14 @@ def index authorize :import @import_type = params.fetch(:import_type, "volunteer") @import_error = session[:import_error] + @sms_opt_in_warning = session[:sms_opt_in_warning] session[:import_error] = nil + session[:sms_opt_in_warning] = nil end def create authorize :import - import = import_from_csv(params[:import_type], params[:file], current_user.casa_org_id) + import = import_from_csv(params[:import_type], params[:sms_opt_in], params[:file], current_user.casa_org_id) message = import[:message] # If there were failed imports @@ -23,6 +27,8 @@ def create if import[:type] == :error session[:import_error] = message + elsif import[:type] == :sms_opt_in_warning + session[:sms_opt_in_warning] = import[:import_type] # Only use flash for success messages. Otherwise may cause CookieOverflow else flash[:success] = message @@ -60,11 +66,15 @@ def header_valid?(file_header, import_type) file_header == header[import_type] end - def import_from_csv(import_type, file, org_id) + def import_from_csv(import_type, sms_opt_in, file, org_id) validated_file = validate_file(file, import_type) return validated_file unless validated_file.nil? + if requires_sms_opt_in(file, import_type, sms_opt_in) + return {type: :sms_opt_in_warning, import_type: import_type} + end + case import_type when "volunteer" VolunteerImporter.import_volunteers(file, org_id) @@ -105,4 +115,23 @@ def validate_file(file, import_type) {type: :error, message: message} end end + + def requires_sms_opt_in(file, import_type, sms_opt_in) + if (import_type == "volunteer" || import_type == "supervisor") && import_contains_phone_numbers(file) + return sms_opt_in != "1" + end + + false + end + + def import_contains_phone_numbers(file) + CSV.foreach(file, headers: true, header_converters: :symbol) do |row| + phone_number = row[:phone_number] + if !phone_number.nil? && !phone_number.strip.empty? + return true + end + end + + false + end end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 4e58958d21..5bec593ceb 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -30,6 +30,7 @@ require('src/emancipations') require('src/select') require('src/dashboard') require('src/sidebar') +require('src/import') require('src/readMore') require('src/tooltip') require('src/password_confirmation') diff --git a/app/javascript/src/import.js b/app/javascript/src/import.js new file mode 100644 index 0000000000..15dd38700f --- /dev/null +++ b/app/javascript/src/import.js @@ -0,0 +1,71 @@ +/* global atob */ +/* global Blob */ +/* global FileReader */ +/* global localStorage */ +/* global File */ +/* global DataTransfer */ + +function dataURItoBlob (dataURI) { + // convert base64 to raw binary data held in a string + const byteString = atob(dataURI.split(',')[1]) + + // separate out the mime component + const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + + // write the bytes of the string to an ArrayBuffer + const arrayBuffer = new ArrayBuffer(byteString.length) + const _ia = new Uint8Array(arrayBuffer) + for (let i = 0; i < byteString.length; i++) { + _ia[i] = byteString.charCodeAt(i) + } + + const dataView = new DataView(arrayBuffer) + const blob = new Blob([dataView], { type: mimeString }) + return blob +} + +function storeCSVFile (file, key) { + const reader = new FileReader() + reader.onload = (fileEvent) => { + localStorage[key] = JSON.stringify({ + name: file.name, + data: fileEvent.target.result + }) + } + reader.readAsDataURL(file) +} + +function fetchCSVFile (key) { + const storedFileData = JSON.parse(localStorage[key]) + const fileContent = dataURItoBlob(storedFileData.data) + const file = new File([fileContent], storedFileData.name, { type: 'text/csv' }) + return file +} + +function populateFileInput (inputId) { + const csvInput = document.getElementById(inputId) + if (csvInput.files.length === 0 && localStorage[inputId]) { + const file = fetchCSVFile(inputId) + const container = new DataTransfer() + container.items.add(file) + csvInput.files = container.files + } +} + +$('document').ready(() => { + ['volunteer', 'supervisor'].forEach((importType) => { + const inputFileElementId = `${importType}-file` + + document.getElementById(inputFileElementId).addEventListener('change', function (event) { + document.getElementById(`${importType}-import-button`).disabled = event.target.value === '' + const file = document.getElementById(inputFileElementId).files[0] + storeCSVFile(file, inputFileElementId) + }) + + if (document.getElementById('smsOptIn') == null) { + delete localStorage[inputFileElementId] + } else { + populateFileInput(inputFileElementId) + } + }) +}) diff --git a/app/lib/importers/supervisor_importer.rb b/app/lib/importers/supervisor_importer.rb index 9d269e84e0..23cdefe436 100644 --- a/app/lib/importers/supervisor_importer.rb +++ b/app/lib/importers/supervisor_importer.rb @@ -18,6 +18,7 @@ def import_supervisors end supervisor_params[:phone_number] = supervisor_params.key?(:phone_number) ? "+#{supervisor_params[:phone_number]}" : "" + supervisor_params[:receive_sms_notifications] = !supervisor_params[:phone_number].empty? supervisor = Supervisor.find_by(email: supervisor_params[:email]) volunteer_assignment_list = email_addresses_to_users(Volunteer, String(row[:supervisor_volunteers])) @@ -32,15 +33,7 @@ def import_supervisors supervisor = create_user_record(Supervisor, supervisor_params) end - volunteer_assignment_list.each do |volunteer| - if volunteer.supervisor - next if volunteer.supervisor == supervisor - - raise "Volunteer #{volunteer.email} already has a supervisor" - else - supervisor.volunteers << volunteer - end - end + assign_volunteers(supervisor, volunteer_assignment_list) end end @@ -49,4 +42,14 @@ def update_supervisor(supervisor, supervisor_params, volunteer_assignment_list) supervisor.update(supervisor_params) end end + + def assign_volunteers(supervisor, volunteer_assignment_list) + volunteer_assignment_list.select { |v| v.supervisor != supervisor }.each do |volunteer| + if volunteer.supervisor + raise "Volunteer #{volunteer.email} already has a supervisor" + else + supervisor.volunteers << volunteer + end + end + end end diff --git a/app/lib/importers/volunteer_importer.rb b/app/lib/importers/volunteer_importer.rb index 4485c3730c..c805498137 100644 --- a/app/lib/importers/volunteer_importer.rb +++ b/app/lib/importers/volunteer_importer.rb @@ -18,6 +18,7 @@ def import_volunteers end volunteer_params[:phone_number] = volunteer_params.key?(:phone_number) ? "+#{volunteer_params[:phone_number]}" : "" + volunteer_params[:receive_sms_notifications] = !volunteer_params[:phone_number].empty? volunteer = Volunteer.find_by(email: volunteer_params[:email]) diff --git a/app/views/imports/_sms_opt_in_modal.html.erb b/app/views/imports/_sms_opt_in_modal.html.erb new file mode 100644 index 0000000000..e46dc24d8d --- /dev/null +++ b/app/views/imports/_sms_opt_in_modal.html.erb @@ -0,0 +1,33 @@ + diff --git a/app/views/imports/_supervisors.html.erb b/app/views/imports/_supervisors.html.erb index f84e3d86ed..c7a05852aa 100644 --- a/app/views/imports/_supervisors.html.erb +++ b/app/views/imports/_supervisors.html.erb @@ -13,7 +13,7 @@ 2. <%= t(".upload_title") %> - <%= form_with(url: imports_path, local: :true) do |f| %> + <%= form_with(url: imports_path, local: :true, id: "supervisor-import-form") do |f| %> <%= f.hidden_field :import_type, value: "supervisor" %>