diff --git a/.rubocop.yml b/.rubocop.yml index ddbc53a..2f5dff1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,6 +5,9 @@ AllCops: - bin/**/* - vendor/assets/**/* +Lint/Debugger: + AutoCorrect: False + Style/AndOr: Enabled: false diff --git a/.sass-lint.yml b/.sass-lint.yml new file mode 100644 index 0000000..ade2f30 --- /dev/null +++ b/.sass-lint.yml @@ -0,0 +1,52 @@ +files: + include: site/**/*.scss + ignore: + - site/assets/scss/*bootstrap* + +options: + formatter: stylish + merge-default-rules: true + +# https://github.com/sasstools/sass-lint/tree/master/docs/rules +rules: + + class-name-format: + - 1 + - convention: 'hyphenatedbem' + + force-pseudo-nesting: 0 + + id-name-format: 0 + + leading-zero: + - 1 + - include: true + + nesting-depth: + - 1 + - max-depth: 4 + + no-css-comments: 0 + + no-color-literals: + - 1 + - + allow-rgba: true + + no-duplicate-properties: 1 + + no-qualifying-elements: + - 1 + - allow-element-with-attribute: true # input[type='email'] but not div.class-name + + no-vendor-prefixes: 1 + + property-sort-order: + - 1 + - + # https://github.com/sasstools/sass-lint/blob/develop/lib/config/property-sort-orders/concentric.yml + order: concentric + # https://github.com/sasstools/sass-lint/blob/develop/lib/config/property-sort-orders/smacss.yml + # order: smacss + + quotes: 0 diff --git a/Gemfile b/Gemfile index bd5d9ab..d8447a6 100644 --- a/Gemfile +++ b/Gemfile @@ -4,11 +4,12 @@ source 'https://rubygems.org' gem 'figaro', '~> 1.1.1' gem 'bcrypt', '~> 3.1.7' gem 'mysql2', '>= 0.3.18', '< 0.5' -gem 'rails', '~> 5.0', '>= 5.0.0.1' +gem 'rails', '~> 5.0', '>= 5.0.1' gem 'jbuilder', '~> 2.6' gem 'jquery-rails' gem 'json', '~> 2.0.2' +gem 'kaminari' gem 'mailjet', '~> 1.3.8' gem 'puma', '~> 3.0' gem 'pundit' @@ -53,6 +54,7 @@ group :development, :test do gem 'byebug', platform: :mri gem 'pry-byebug' gem 'pry-rails' + gem 'faker' gem 'brakeman' gem 'rubocop', '~> 0.42.0' diff --git a/Gemfile.lock b/Gemfile.lock index 589a2b6..bbe87c3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,46 +1,47 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.0.0.1) - actionpack (= 5.0.0.1) + actioncable (5.0.1) + actionpack (= 5.0.1) nio4r (~> 1.2) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) + actionmailer (5.0.1) + actionpack (= 5.0.1) + actionview (= 5.0.1) + activejob (= 5.0.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.0.1) - actionview (= 5.0.0.1) - activesupport (= 5.0.0.1) + actionpack (5.0.1) + actionview (= 5.0.1) + activesupport (= 5.0.1) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.1) - activesupport (= 5.0.0.1) + actionview (5.0.1) + activesupport (= 5.0.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.1) - activesupport (= 5.0.0.1) + activejob (5.0.1) + activesupport (= 5.0.1) globalid (>= 0.3.6) - activemodel (5.0.0.1) - activesupport (= 5.0.0.1) - activerecord (5.0.0.1) - activemodel (= 5.0.0.1) - activesupport (= 5.0.0.1) + activemodel (5.0.1) + activesupport (= 5.0.1) + activerecord (5.0.1) + activemodel (= 5.0.1) + activesupport (= 5.0.1) arel (~> 7.0) - activesupport (5.0.0.1) + activesupport (5.0.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.4.0) + addressable (2.5.0) + public_suffix (~> 2.0, >= 2.0.2) ansi (1.5.0) - arel (7.1.1) + arel (7.1.4) ast (2.3.0) awesome_print (1.7.0) bcrypt (3.1.11) @@ -53,29 +54,31 @@ GEM bourbon (4.2.7) sass (~> 3.4) thor (~> 0.19) - brakeman (3.4.0) + brakeman (3.4.1) builder (3.2.2) - byebug (9.0.5) + byebug (9.0.6) choice (0.2.0) coderay (1.1.1) - concurrent-ruby (1.0.2) - css_parser (1.4.5) + concurrent-ruby (1.0.4) + css_parser (1.4.7) addressable debug_inspector (0.0.2) docile (1.1.5) - domain_name (0.5.20160615) + domain_name (0.5.20161129) unf (>= 0.0.5, < 1.0.0) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) erubis (2.7.0) - eventmachine (1.2.0.1) + eventmachine (1.2.1) execjs (2.7.0) + faker (1.7.3) + i18n (~> 0.5) ffi (1.9.14) figaro (1.1.1) thor (~> 0.14) formatador (0.2.5) - foundation_emails (2.2.0.0) + foundation_emails (2.2.1.0) globalid (0.3.7) activesupport (>= 4.1.0) guard (2.14.0) @@ -106,27 +109,40 @@ GEM guard (>= 2.0.0) guard-compat (~> 1.0) htmlentities (4.3.4) - http-cookie (1.0.2) + http-cookie (1.0.3) domain_name (~> 0.5) http_parser.rb (0.6.0) i18n (0.7.0) - inky-rb (1.3.6.1) + inky-rb (1.3.7.2) foundation_emails (~> 2) - jbuilder (2.6.0) + nokogiri + jbuilder (2.6.1) activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) - jquery-rails (4.2.1) + jquery-rails (4.2.2) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.0.2) + json (2.0.3) + kaminari (1.0.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.0.1) + kaminari-activerecord (= 1.0.1) + kaminari-core (= 1.0.1) + kaminari-actionview (1.0.1) + actionview + kaminari-core (= 1.0.1) + kaminari-activerecord (1.0.1) + activerecord + kaminari-core (= 1.0.1) + kaminari-core (1.0.1) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) loofah (2.0.3) nokogiri (>= 1.5.9) - lumberjack (1.0.10) + lumberjack (1.0.11) mail (2.6.4) mime-types (>= 1.16, < 4) mailjet (1.3.8) @@ -138,29 +154,27 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) - minitest (5.9.0) - minitest-reporters (1.1.11) + minitest (5.10.1) + minitest-reporters (1.1.13) ansi builder minitest (>= 5.0) ruby-progressbar multi_json (1.12.1) - mysql2 (0.4.4) + mysql2 (0.4.5) neat (1.8.0) sass (>= 3.3) thor (~> 0.19) nenv (0.3.0) netrc (0.11.0) nio4r (1.2.1) - nokogiri (1.6.8) + nokogiri (1.7.0.1) mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) notiffany (0.1.1) nenv (~> 0.1) shellany (~> 0.0) - parser (2.3.1.2) + parser (2.3.3.1) ast (~> 2.2) - pkg-config (1.1.7) policy-assertions (0.0.3) activesupport (>= 3.0.0) pundit (>= 1.0.0) @@ -168,19 +182,20 @@ GEM premailer (1.8.7) css_parser (>= 1.4.5) htmlentities (>= 4.0.0) - premailer-rails (1.9.4) + premailer-rails (1.9.5) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - pry-byebug (3.4.0) + pry-byebug (3.4.2) byebug (~> 9.0) pry (~> 0.10) pry-rails (0.3.4) pry (>= 0.9.10) - puma (3.6.0) + public_suffix (2.0.5) + puma (3.6.2) pundit (1.1.0) activesupport (>= 3.0.0) rack (2.0.1) @@ -188,25 +203,25 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (5.0.0.1) - actioncable (= 5.0.0.1) - actionmailer (= 5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) - activemodel (= 5.0.0.1) - activerecord (= 5.0.0.1) - activesupport (= 5.0.0.1) + rails (5.0.1) + actioncable (= 5.0.1) + actionmailer (= 5.0.1) + actionpack (= 5.0.1) + actionview (= 5.0.1) + activejob (= 5.0.1) + activemodel (= 5.0.1) + activerecord (= 5.0.1) + activesupport (= 5.0.1) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.1) + railties (= 5.0.1) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.1) actionpack (~> 5.x) actionview (~> 5.x) activesupport (~> 5.x) - rails-dom-testing (2.0.1) + rails-dom-testing (2.0.2) activesupport (>= 4.2.0, < 6.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-erd (1.5.0) activerecord (>= 3.2) activesupport (>= 3.2) @@ -214,15 +229,15 @@ GEM ruby-graphviz (~> 1.2) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (5.0.0.1) - actionpack (= 5.0.0.1) - activesupport (= 5.0.0.1) + railties (5.0.1) + actionpack (= 5.0.1) + activesupport (= 5.0.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.1.0) - rake (11.2.2) - rb-fsevent (0.9.7) + rainbow (2.2.1) + rake (12.0.0) + rb-fsevent (0.9.8) rb-inotify (0.9.7) ffi (>= 0.5.0) rest-client (2.0.0) @@ -237,8 +252,8 @@ GEM unicode-display_width (~> 1.0, >= 1.0.1) ruby-graphviz (1.2.2) ruby-progressbar (1.8.1) - ruby_dep (1.4.0) - sass (3.4.22) + ruby_dep (1.5.0) + sass (3.4.23) sass-rails (5.0.6) railties (>= 4.0.0, < 6) sass (~> 3.1) @@ -253,18 +268,19 @@ GEM simplecov-html (~> 0.10.0) simplecov-html (0.10.0) slop (3.6.0) - spring (1.7.2) - spring-watcher-listen (2.0.0) + spring (2.0.0) + activesupport (>= 4.2) + spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) - spring (~> 1.2) - sprockets (3.7.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.1.1) + sprockets-rails (3.2.0) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - thor (0.19.1) + thor (0.19.4) thread_safe (0.3.5) tilt (2.0.5) turbolinks (5.0.1) @@ -272,13 +288,13 @@ GEM turbolinks-source (5.0.0) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (3.0.2) + uglifier (3.0.4) execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext unf_ext (0.0.7.2) - unicode-display_width (1.1.1) - web-console (3.3.1) + unicode-display_width (1.1.2) + web-console (3.4.0) actionview (>= 5.0) activemodel (>= 5.0) debug_inspector @@ -298,6 +314,7 @@ DEPENDENCIES bourbon brakeman byebug + faker figaro (~> 1.1.1) guard guard-brakeman @@ -309,6 +326,7 @@ DEPENDENCIES jbuilder (~> 2.6) jquery-rails json (~> 2.0.2) + kaminari listen mailjet (~> 1.3.8) minitest-reporters @@ -321,7 +339,7 @@ DEPENDENCIES puma (~> 3.0) pundit rack-livereload - rails (~> 5.0, >= 5.0.0.1) + rails (~> 5.0, >= 5.0.1) rails-controller-testing rails-erd rubocop (~> 0.42.0) @@ -336,4 +354,4 @@ DEPENDENCIES web-console BUNDLED WITH - 1.12.5 + 1.13.3 diff --git a/Guardfile b/Guardfile index 1027353..305fd8e 100644 --- a/Guardfile +++ b/Guardfile @@ -79,7 +79,7 @@ guard :shell, all_on_start: true do end end -guard :rubocop, cli: %w(-D -S) do +guard :rubocop, cli: %w(-D -S -a) do watch(/.rubocop.yml/) watch(/.+\.rb$/) watch(/Rakefile/) diff --git a/app/assets/images/ic_arrow_drop_down_black_24dp_1x.png b/app/assets/images/ic_arrow_drop_down_black_24dp_1x.png new file mode 100644 index 0000000..4b1be1c Binary files /dev/null and b/app/assets/images/ic_arrow_drop_down_black_24dp_1x.png differ diff --git a/app/assets/images/ic_arrow_drop_down_black_24dp_2x.png b/app/assets/images/ic_arrow_drop_down_black_24dp_2x.png new file mode 100644 index 0000000..feb901a Binary files /dev/null and b/app/assets/images/ic_arrow_drop_down_black_24dp_2x.png differ diff --git a/app/assets/images/ic_arrow_drop_up_black_24dp_1x.png b/app/assets/images/ic_arrow_drop_up_black_24dp_1x.png new file mode 100644 index 0000000..8416ffb Binary files /dev/null and b/app/assets/images/ic_arrow_drop_up_black_24dp_1x.png differ diff --git a/app/assets/images/ic_arrow_drop_up_black_24dp_2x.png b/app/assets/images/ic_arrow_drop_up_black_24dp_2x.png new file mode 100644 index 0000000..3d24376 Binary files /dev/null and b/app/assets/images/ic_arrow_drop_up_black_24dp_2x.png differ diff --git a/app/assets/images/ic_sort_black_24dp_1x.png b/app/assets/images/ic_sort_black_24dp_1x.png new file mode 100644 index 0000000..04a12a4 Binary files /dev/null and b/app/assets/images/ic_sort_black_24dp_1x.png differ diff --git a/app/assets/images/ic_sort_black_24dp_2x.png b/app/assets/images/ic_sort_black_24dp_2x.png new file mode 100644 index 0000000..9e4fd61 Binary files /dev/null and b/app/assets/images/ic_sort_black_24dp_2x.png differ diff --git a/app/assets/javascripts/ajax-links.js b/app/assets/javascripts/ajax-links.js index 55ee365..4a700c2 100644 --- a/app/assets/javascripts/ajax-links.js +++ b/app/assets/javascripts/ajax-links.js @@ -18,11 +18,6 @@ function updateVotes(data){ $("[data-id=my-vote]").html(data.myVote); } -function updateVeto(data){ - $("[data-id=interview-request]").html(data.requestCopy); - $("[data-id=interview-decline]").html(data.declineCopy); -} - $(document).ready(function() { $('[data-id=ajax-action]').each(function(){ handleAjaxResponse($(this)); }); }); @@ -30,7 +25,3 @@ $(document).ready(function() { $(document).ready(function() { $('[data-id=vote-count]').each(function(){ handleAjaxResponse($(this), updateVotes); }); }); - -$(document).ready(function() { - $('[data-id=veto-status]').each(function(){ handleAjaxResponse($(this), updateVeto); }); -}); diff --git a/app/assets/stylesheets/molecules/_admin_review.scss b/app/assets/stylesheets/molecules/_admin_review.scss index 586367d..f6776a5 100644 --- a/app/assets/stylesheets/molecules/_admin_review.scss +++ b/app/assets/stylesheets/molecules/_admin_review.scss @@ -1,12 +1,14 @@ .admin-review { counter-reset: question; + float: left; + width: 66%; form { margin-left: 2.3em; position: relative; &::before { - content: counter(question) ") "; + content: counter(question) ') '; counter-increment: question; font-size: 1.25em; left: -1.8em; @@ -17,6 +19,67 @@ } +.review-comments { + float: right; + padding-left: 30px; + width: 33%; + + > div { + margin-bottom: 30px; + } + + form { + margin-bottom: 30px; + } + + .comment-message { + margin-right: 5px; + } + + .comment-author { + font-size: 0.85em; + font-weight: 700; + margin-bottom: 30px; + text-align: right; + + &::before { + content: '- '; + } + } + + .comment-edit-stamp { + color: rgba($gray-dark, 0.65); + font-size: 0.75em; + text-align: right; + } + + .comment-edit-btn { + cursor: pointer; + display: inline-block; + font-size: 0.85em; + font-weight: bold; + padding: 2px 5px; + + &:hover { + background-color: $gray-base; + color: $gray-lighter; + } + } + + .comment-edit-form { + display: none; + margin: 30px 0; + padding: 10px 0; + + } + + [type="checkbox"] { + &:checked + .comment-edit-form { + display: block; + } + } +} + .review_meta { @media screen and (min-width: 768px) { @@ -24,8 +87,32 @@ & > div { flex: 1 1 auto; } } - .review_meta__votes, - .review_meta__vetos { + .review_meta__votes { + margin-bottom: 15px; a { padding: 5px; } } + + .review_meta__vetos { + label { + display: inline-block; + margin-left: 15px; + } + + .review-status-comments { + opacity: 0; + padding: 15px 0; + height: 0; + max-height: 0; + overflow: hidden; + transition: all 0.500s; + } + + input:checked ~ .review-status-comments { + opacity: 1; + height: auto; + max-height: 9999px; + overflow: auto; + transition: all 0.7500s; + } + } } diff --git a/app/assets/stylesheets/molecules/_tables.scss b/app/assets/stylesheets/molecules/_tables.scss index 2f23953..c1962a1 100644 --- a/app/assets/stylesheets/molecules/_tables.scss +++ b/app/assets/stylesheets/molecules/_tables.scss @@ -9,6 +9,50 @@ th { font-weight: 600; padding: $small-spacing 0; text-align: left; + + a { + display: inline-block; + margin-right: 18px; + padding-right: 5px; + position: relative; + text-decoration: none; + + &::after { + background-image: asset_data_url("ic_sort_black_24dp_2x.png"); + background-repeat: no-repeat; + background-size: contain; + content: ""; + display: block; + height: 18px; + left: 100%; + opacity: 0.5; + position: absolute; + top: 4px; + width: 18px; + } + + &.asc { + &::after { + background-image: asset_data_url("ic_arrow_drop_up_black_24dp_2x.png"); + height: 25px; + left: calc(100% - 5px); + opacity: 1; + top: 1px; + width: 25px; + } + } + + &.desc { + &::after { + background-image: asset_data_url("ic_arrow_drop_down_black_24dp_2x.png"); + height: 25px; + left: calc(100% - 5px); + opacity: 1; + top: 1px; + width: 25px; + } + } + } } td { diff --git a/app/controllers/admin/auth_controller.rb b/app/controllers/admin/auth_controller.rb index afc039f..763db50 100644 --- a/app/controllers/admin/auth_controller.rb +++ b/app/controllers/admin/auth_controller.rb @@ -15,7 +15,7 @@ module Admin if user && user.authenticate(auth_params[:password]) session[:user] = user.to_i - redirect_to admin_path + redirect_to session[:request] || admin_path else redirect_to admin_login_path, flash: { error: "Sorry, incorrect email or password. Please try again." } diff --git a/app/controllers/admin/candidate_controller.rb b/app/controllers/admin/candidate_controller.rb index 5a95b68..079ecea 100644 --- a/app/controllers/admin/candidate_controller.rb +++ b/app/controllers/admin/candidate_controller.rb @@ -4,7 +4,8 @@ module Admin before_action :collect_quizzes, except: [:login, :auth] def index - @candidates = policy_scope Candidate.order(:name) + @candidates = policy_scope Candidate.order("#{sort_column} #{sort_direction}") + .page(params[:page]) end def new @@ -49,13 +50,14 @@ module Admin authorize Candidate candidate = Candidate.find_by(id: params[:id]) CandidateMailer.welcome(candidate).deliver_later - render json: { message: "Email queued!" }.to_json end private def candidate_params - params.require(:candidate).permit(:name, :email, :experience, :quiz_id) + params.require(:candidate).permit( + :name, :email, :experience, :quiz_id, :project, :position, :skill_needs + ) end def collect_quizzes @@ -66,5 +68,9 @@ module Admin CandidateMailer.welcome(candidate).deliver_later RecruiterMailer.candidate_created(candidate).deliver_later end + + def sort_column + Candidate.column_names.include?(params[:sort]) ? params[:sort] : 'name' + end end end diff --git a/app/controllers/admin/comment_controller.rb b/app/controllers/admin/comment_controller.rb new file mode 100644 index 0000000..6da6843 --- /dev/null +++ b/app/controllers/admin/comment_controller.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true +module Admin + class CommentController < AdminController + def update + comment = QuizComment.find_by(id: params[:id], test_hash: params[:test_hash]) + authorize comment + + comment.update(comment_params) + flash_message = if comment.save + { success: "Sucessfully updated comment" } + else + { error: "Failed to update comment" } + end + redirect_to admin_result_path(params[:test_hash]), flash: flash_message + end + + def create + comment = QuizComment.new(comment_params.merge(user_id: current_user.id, test_hash: params[:test_hash])) + authorize comment + + flash_message = if comment.save + { success: "Sucessfully created comment" } + else + { error: "Failed to save comment" } + end + + ReviewerMailer.new_comment(comment).deliver_later if comment.persisted? + redirect_to admin_result_path(params[:test_hash]), flash: flash_message + end + + private + + def comment_params + params.require(:quiz_comment).permit(:message) + end + end +end diff --git a/app/controllers/admin/result_controller.rb b/app/controllers/admin/result_controller.rb index e5c9564..48bb2b5 100644 --- a/app/controllers/admin/result_controller.rb +++ b/app/controllers/admin/result_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Admin class ResultController < AdminController - # # TODO: change context from Candidate to Quiz # bypass pundit lockdowns until completed after_action :skip_policy_scope @@ -10,13 +9,30 @@ module Admin # TODO: Limit results to the quizzes current_user has access to def index - @candidates = Candidate.where(completed: true).includes(:recruiter) + sort_case = "(case when review_status = 0 then '' else name end)" + sort_with_case = sort_column == 'name' ? sort_case : sort_column + @candidates = Candidate.where(completed: true) + .includes(:recruiter) + .order("#{sort_with_case} #{sort_direction}") + .page(params[:page]) end def view @candidate = Candidate.find_by(test_hash: params[:test_hash]) @quiz = @candidate.my_quiz @status = QuizStatus.new(@candidate) + @comments = QuizComment.includes(:user).where(test_hash: @candidate.test_hash).order(:created_at) + @comment = QuizComment.new end + + private + + def sort_column + @sort_col ||= Candidate.column_names.include?(params[:sort]) ? params[:sort] : 'completed_at' + end + + def sort_direction + %w(asc desc).include?(params[:direction]) ? params[:direction] : 'desc' + end end end diff --git a/app/controllers/admin/user_controller.rb b/app/controllers/admin/user_controller.rb index d8ab69a..ead944c 100644 --- a/app/controllers/admin/user_controller.rb +++ b/app/controllers/admin/user_controller.rb @@ -2,7 +2,8 @@ module Admin class UserController < AdminController def index - @users = policy_scope User.order(:name) + @users = policy_scope User.order("#{sort_column} #{sort_direction}") + .page(params[:page]) end def new @@ -52,5 +53,9 @@ module Admin def user_params params.require(:user).permit(policy(User).permitted_attributes) end + + def sort_column + User.column_names.include?(params[:sort]) ? params[:sort] : 'name' + end end end diff --git a/app/controllers/admin/vote_controller.rb b/app/controllers/admin/vote_controller.rb index 3a39bb2..862410a 100644 --- a/app/controllers/admin/vote_controller.rb +++ b/app/controllers/admin/vote_controller.rb @@ -5,54 +5,41 @@ module Admin @candidate = Candidate.find_by(test_hash: params[:test_hash]) authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id) current_user.cast_yea_on(@candidate) - - results = { - message: "Vote Counted", - upCount: @candidate.votes.yea.count, - downCount: @candidate.votes.nay.count, - myVote: "yea" - } - render json: results.to_json end def down @candidate = Candidate.find_by(test_hash: params[:test_hash]) authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id) current_user.cast_nay_on(@candidate) - - results = { - message: "Vote Counted", - upCount: @candidate.votes.yea.count, - downCount: @candidate.votes.nay.count, - myVote: "nay" - } - render json: results.to_json end - def approve + def interview_request @candidate = Candidate.find_by(test_hash: params[:test_hash]) authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id) - current_user.approve_candidate(@candidate) - results = { - message: "Interview requested!", - requestCopy: "Requested", - declineCopy: "Decline Interview" - } - render json: results.to_json + if interview_params[:review_comments].blank? + refuse_interview_request + else + send_interview_request + end end - def decline - @candidate = Candidate.find_by(test_hash: params[:test_hash]) - authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id) - current_user.decline_candidate(@candidate) + private - results = { - message: "Interview declined.", - requestCopy: "Request Interview", - declineCopy: "Declined" - } - render json: results.to_json - end + def refuse_interview_request + redirect_to admin_result_path(@candidate.test_hash), + flash: { error: "Must provide a comment" } + end + + def send_interview_request + current_user.review_candidate(@candidate, interview_params) + RecruiterMailer.candidate_reviewed(@candidate).deliver_later + redirect_to admin_result_path(@candidate.test_hash), + flash: { notice: "Quiz #{interview_params[:review_status]}" } + end + + def interview_params + params.permit(:review_status, :review_comments) + end end end diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 2c2daf6..327e1f7 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -9,6 +9,9 @@ class AdminController < ApplicationController after_action :verify_authorized, except: :index after_action :verify_policy_scoped, only: :index + helper_method :sort_direction + helper_method :sort_column + def current_user @current_user ||= User.find_by(id: session[:user]) if session[:user] end @@ -16,7 +19,16 @@ class AdminController < ApplicationController private + def sort_column + :completed_at + end + + def sort_direction + %w(asc desc).include?(params[:direction]) ? params[:direction] : 'asc' + end + def authorize_user + session[:request] = request.fullpath redirect_to admin_login_path unless current_user end diff --git a/app/controllers/quiz_controller.rb b/app/controllers/quiz_controller.rb index 4f4e35d..6ec5921 100644 --- a/app/controllers/quiz_controller.rb +++ b/app/controllers/quiz_controller.rb @@ -35,7 +35,7 @@ class QuizController < ApplicationController private def complete_and_email - if current_candidate.update_attributes(completed: true) + if current_candidate.update_attributes(completed: true, completed_at: DateTime.current) current_candidate.build_reviews CandidateMailer.submitted(current_candidate).deliver_later RecruiterMailer.candidate_submitted(current_candidate).deliver_later diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 11f160f..9afc636 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -44,4 +44,11 @@ module ApplicationHelper @js_blocks << code_label content_for :custom_javascipt, &block end + + def sortable(column, title = nil) + title ||= column.titleize + css_class = column == sort_column ? sort_direction.to_s : nil + direction = column == sort_column && sort_direction == "desc" ? "asc" : "desc" + link_to title, { sort: column, direction: direction }, class: css_class + end end diff --git a/app/mailers/recruiter_mailer.rb b/app/mailers/recruiter_mailer.rb index 8ef985c..d9dff41 100644 --- a/app/mailers/recruiter_mailer.rb +++ b/app/mailers/recruiter_mailer.rb @@ -1,14 +1,23 @@ # frozen_string_literal: true class RecruiterMailer < ApplicationMailer def candidate_created candidate - @candidate = candidate + @candidate = Candidate.find_by(id: candidate.to_i) - mail to: @candidate.recruiter.email, subject: "Skills Assessment Test - #{candidate.name}" + mail to: @candidate.recruiter.email, + subject: "Skills Assessment Test - #{candidate.name}" end def candidate_submitted candidate - @candidate = candidate + @candidate = Candidate.find_by(id: candidate.to_i) - mail to: @candidate.recruiter.email, subject: "Skills Assessment Test - #{candidate.name}" + mail to: @candidate.recruiter.email, + subject: "Skills Assessment Test - #{candidate.name}" + end + + def candidate_reviewed candidate + @candidate = Candidate.find_by(id: candidate.to_i) + + mail to: @candidate.recruiter.email, + subject: "Skills Assesment - Review Complete for #{candidate.name}" end end diff --git a/app/mailers/reviewer_mailer.rb b/app/mailers/reviewer_mailer.rb index 9d57d89..b640169 100644 --- a/app/mailers/reviewer_mailer.rb +++ b/app/mailers/reviewer_mailer.rb @@ -6,4 +6,24 @@ class ReviewerMailer < ApplicationMailer mail to: recipients, subject: "Skills Assessment Results - #{@candidate.test_hash}" end + + def reminder reminder + @reminder = reminder + + mail to: reminder.email, subject: "Review Reminder" + end + + def notify_manager candidate_id + @candidate = Candidate.find_by(id: candidate_id) + @manager = @candidate.manager + + mail to: @manager.email, subject: "Voting Complete" + end + + def new_comment comment + @comment = comment + recipients = comment.candidate.reviewers.map(&:email) + + mail to: recipients, subject: "Skills Assessment Review Comment - #{@comment.test_hash}" + end end diff --git a/app/models/candidate.rb b/app/models/candidate.rb index f75a738..56083a1 100644 --- a/app/models/candidate.rb +++ b/app/models/candidate.rb @@ -6,6 +6,7 @@ class Candidate < ApplicationRecord belongs_to :recruiter, class_name: "User" has_many :votes, class_name: "ReviewerVote" has_many :reviewers, through: :quiz + has_many :quiz_comments, foreign_key: :test_hash, primary_key: :test_hash serialize :email, CryptSerializer @@ -14,6 +15,8 @@ class Candidate < ApplicationRecord validates :recruiter_id, presence: true validates :name, presence: true validates :experience, presence: true + validates :project, presence: true + validates :position, presence: true validates :email, uniqueness: true, presence: true, email_format: true validates :test_hash, uniqueness: true, presence: true @@ -23,12 +26,24 @@ class Candidate < ApplicationRecord declined: 2 } + def interview? + return 'yes' if approved? + 'no' if declined? + end + def build_reviews reviewers.each do |reviewer| votes.find_or_create_by(user_id: reviewer.id) end end + def manager + manager_votes = votes.joins(:user).where("users.role = 'manager'") + return User.new(name: "No Manager") if manager_votes.empty? + + manager_votes.first.user + end + def submitted_answers answers.where(submitted: true) end diff --git a/app/models/quiz_comment.rb b/app/models/quiz_comment.rb new file mode 100644 index 0000000..a918447 --- /dev/null +++ b/app/models/quiz_comment.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +class QuizComment < ApplicationRecord + belongs_to :user + belongs_to :candidate, foreign_key: :test_hash, primary_key: :test_hash + + validates :message, presence: true + + def edits? + updated_at > (created_at + 5.seconds) + end +end diff --git a/app/models/reviewer_vote.rb b/app/models/reviewer_vote.rb index 6ed8fce..26770f3 100644 --- a/app/models/reviewer_vote.rb +++ b/app/models/reviewer_vote.rb @@ -5,6 +5,8 @@ class ReviewerVote < ApplicationRecord validates :user_id, uniqueness: { scope: :candidate_id } + after_save :notify_manager + enum vote: { undecided: 0, yea: 1, @@ -13,6 +15,40 @@ class ReviewerVote < ApplicationRecord enum veto: { approved: 1, - rejected: 2 + declined: 2 } + + private + + def notify_manager + ReviewerMailer.notify_manager(candidate_id).deliver_now if all_reviewers_voted? + end + + def all_reviewers_voted? # rubocop:disable Metrics/MethodLength + sql = " select distinct rev.candidate_id + , full_votes.voters, full_votes.vote_count + , c.test_hash + from reviewer_votes rev + inner join users u on u.id = rev.user_id + inner join candidates c on c.id = rev.candidate_id + left join ( + select candidate_id + from reviewer_votes + where veto > 0 or veto is null + ) as vetos on vetos.candidate_id = rev.candidate_id + left join ( + select candidate_id + , count(vote) voters + , sum(case when vote != 0 then 1 else 0 end) vote_count + from reviewer_votes + left join users on users.id = reviewer_votes.user_id + where users.role != 'manager' + group by candidate_id + ) as full_votes on full_votes.candidate_id = rev.candidate_id + where vetos.candidate_id is null + and full_votes.voters = full_votes.vote_count + and rev.candidate_id = #{candidate_id};" + result = ActiveRecord::Base.connection.exec_query(sql) + result.count == 1 + end # rubocop:enable Metrics/MethodLength end diff --git a/app/models/user.rb b/app/models/user.rb index b7dfbdd..26f5957 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,6 +5,7 @@ class User < ApplicationRecord has_many :reviewer_to_quizzes has_many :quizzes, through: :reviewer_to_quizzes has_many :votes, class_name: 'ReviewerVote' + has_many :quiz_comments has_many :reviewees, through: :quizzes, source: :candidates @@ -18,7 +19,13 @@ class User < ApplicationRecord save end + def commented_on? test_hash + quiz_comments.where(test_hash: test_hash).count.positive? + end + # Voting + # TODO: Refactor this out of User, belongs on ReviewerVote + # ie: cast_yea(candidate, user) def cast_yea_on candidate vote = votes.find_by(candidate_id: candidate.to_i) vote.vote = :yea @@ -31,20 +38,16 @@ class User < ApplicationRecord vote.save end - def approve_candidate candidate + def review_candidate candidate, parms_hash candidate = Candidate.find(candidate.to_i) vote = votes.find_by(candidate_id: candidate.to_i) - vote.veto = :approved - candidate.update_attribute(:review_status, :approved) if vote.save - end - - def decline_candidate candidate - candidate = Candidate.find(candidate.to_i) - - vote = votes.find_by(candidate_id: candidate.to_i) - vote.veto = :rejected - candidate.update_attribute(:review_status, :declined) if vote.save + vote.veto = parms_hash[:review_status] + if vote.save + # skipping validations on candidate because that's not the managers responsibility + candidate.review_comments = parms_hash[:review_comments] + candidate.update_attribute(:review_status, parms_hash[:review_status]) + end end def my_vote candidate diff --git a/app/policies/quiz_comment_policy.rb b/app/policies/quiz_comment_policy.rb new file mode 100644 index 0000000..cb95c15 --- /dev/null +++ b/app/policies/quiz_comment_policy.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +class QuizCommentPolicy < ApplicationPolicy + # Quiz Comment Policy + # + # Anyone who can vote on results, can comment + # Only comment owner can edit her comment + + def new? + user.acts_as_reviewer? + end + + def create? + return true if user.acts_as_admin? + + user.acts_as_reviewer? && + record.candidate.reviewers.where(id: user.id).count.positive? + end + + def update? + user.acts_as_reviewer? && user.id == record.user_id + end +end diff --git a/app/policies/reviewer_vote_policy.rb b/app/policies/reviewer_vote_policy.rb index 7e9f1da..a5d2193 100644 --- a/app/policies/reviewer_vote_policy.rb +++ b/app/policies/reviewer_vote_policy.rb @@ -2,30 +2,26 @@ class ReviewerVotePolicy < ApplicationPolicy # Voting Policy # - # Only Reviewers, Managers, and Admins, can cast a vote on a quiz result + # Only Reviewers and Managers can cast a vote on a quiz result # # Reviewers can vote any quiz they are linked to # Only Managers, and Admins, can veto a quiz result def up? - return true if user.acts_as_admin? + return false unless user.commented_on?(record.candidate.test_hash) return false unless record.candidate.reviewers.include? user + return false if user.admin? user.acts_as_reviewer? end def down? - return true if user.acts_as_admin? + return false unless user.commented_on?(record.candidate.test_hash) return false unless record.candidate.reviewers.include? user + return false if user.admin? user.acts_as_reviewer? end - def approve? - return true if user.acts_as_admin? - return false unless record.candidate.reviewers.include? user - user.acts_as_manager? - end - - def decline? + def interview_request? return true if user.acts_as_admin? return false unless record.candidate.reviewers.include? user user.acts_as_manager? diff --git a/app/views/admin/candidate/_form.html.erb b/app/views/admin/candidate/_form.html.erb index 0310471..1330165 100644 --- a/app/views/admin/candidate/_form.html.erb +++ b/app/views/admin/candidate/_form.html.erb @@ -16,6 +16,24 @@ <%= form.select :experience, experience_options(candidate.experience), include_blank: false %> +
+ <%= form.label :project, "Client or project" %> + <%= form.text_field :project %> +
+ +
+ <%= form.radio_button :position, 'full-time' %> + <%= form.label "position_full-time", "Full-time" %> + + <%= form.radio_button :position, 'contract' %> + <%= form.label :position_contract, "Contract" %> +
+ +
+ <%= form.label :skill_needs, "Specific skill needs" %> + <%= form.text_field :skill_needs %> +
+
<%= form.label :quiz_id, "Quiz" %> <%= form.select :quiz_id, quiz_options(quizzes, candidate.quiz_id), include_blank: (quizzes.size > 1) %> diff --git a/app/views/admin/candidate/index.html.erb b/app/views/admin/candidate/index.html.erb index 67f464b..4dc2cfb 100644 --- a/app/views/admin/candidate/index.html.erb +++ b/app/views/admin/candidate/index.html.erb @@ -9,14 +9,14 @@ - - - - + + + + - - - + + + <% @candidates.each do |candidate| %> @@ -32,8 +32,9 @@ - + <% end %>
CandidateTest IDEmailExperience<%= sortable "name", "Candidate" %><%= sortable "test_hash", "Test ID" %><%= sortable "email" %><%= sortable "experience" %> ProgressCompletedRemindedInterview Request<%= sortable "completed_at", "Completed" %><%= sortable "reminded" %>Interview?
<%= candidate.status %> <%= candidate.completed ? link_to("Submitted", admin_result_path(candidate.test_hash)) : "" %> <%= candidate.reminded ? "Yes" : "" %><%= candidate.review_status unless candidate.pending? %><%= candidate.interview? %>
+ <%= paginate @candidates %> diff --git a/app/views/admin/candidate/resend_welcome.json.erb b/app/views/admin/candidate/resend_welcome.json.erb new file mode 100644 index 0000000..fbba8af --- /dev/null +++ b/app/views/admin/candidate/resend_welcome.json.erb @@ -0,0 +1 @@ +{ "message" : "Email queued!" } diff --git a/app/views/admin/result/_comment.html.erb b/app/views/admin/result/_comment.html.erb new file mode 100644 index 0000000..52d261c --- /dev/null +++ b/app/views/admin/result/_comment.html.erb @@ -0,0 +1,23 @@ +
+ <%= comment.message %> + + <% if policy(comment).update? %> + + <% end %> + + <% if comment.edits? %> +
Updated <%= time_ago_in_words(comment.updated_at) %> ago
+ <% else %> +
<%= time_ago_in_words(comment.created_at) %> ago
+ <% end %> +
+ +
<%= comment.user.name %>
+ + +<% if policy(comment).update? %> + +
+ <%= render partial: 'comment_form', locals: {comment: comment, test_hash: comment.test_hash } %> +
+<% end %> diff --git a/app/views/admin/result/_comment_form.html.erb b/app/views/admin/result/_comment_form.html.erb new file mode 100644 index 0000000..f67fd03 --- /dev/null +++ b/app/views/admin/result/_comment_form.html.erb @@ -0,0 +1,23 @@ +<% if comment.id.nil? %> + + <%= form_for comment, url: admin_create_comment_path(test_hash: test_hash), method: :post do |form| %> +
+ <%= form.label :message, "New Comment" %> + <%= form.text_area :message %> +
+ + <%= submit_tag "Save Comment" %> + <% end %> + +<% else %> + + <%= form_for comment, url: admin_update_comment_path(test_hash: test_hash, id: comment.id), method: :post do |form| %> +
+ <%= form.label :message, "Update Comment" %> + <%= form.text_area :message %> +
+ + <%= submit_tag "Update" %> + <% end %> + +<% end %> diff --git a/app/views/admin/result/_voting.html.erb b/app/views/admin/result/_voting.html.erb index bc04725..4029c52 100644 --- a/app/views/admin/result/_voting.html.erb +++ b/app/views/admin/result/_voting.html.erb @@ -1,34 +1,54 @@ -<% # TODO: This needs to be extracted into a decorator, or something. It is only a quick hack solution. %> +<% # TODO: This should be extracted into a decorator, or something. It is only a quick hack solution. %> <% if current_user.acts_as_reviewer? %>
Votes: - <%= link_to admin_up_vote_path(test_hash: @candidate.test_hash), remote: true do %> - Yea (<%= @candidate.votes.yea.count %>) - <% end %> - <%= link_to admin_down_vote_path(test_hash: @candidate.test_hash), remote: true do %> - Nay (<%= @candidate.votes.nay.count %>) + + <% if @candidate.pending? && current_user.commented_on?(@candidate.test_hash) %> + <%= link_to admin_up_vote_path(test_hash: @candidate.test_hash), remote: true do %> + Yea (<%= @candidate.votes.yea.count %>) + <% end %> + + <%= link_to admin_down_vote_path(test_hash: @candidate.test_hash), remote: true do %> + Nay (<%= @candidate.votes.nay.count %>) + <% end %> + <% elsif @candidate.pending? %> +
You must comment before you can vote
+ Yea (<%= @candidate.votes.yea.count %>) + Nay (<%= @candidate.votes.nay.count %>) + <% else %> + Voting closed - + Yea (<%= @candidate.votes.yea.count %>) - + Nay (<%= @candidate.votes.nay.count %>) <% end %> + (Your vote: <%= current_user.my_vote(@candidate) %>)
<% end %> <% if current_user.acts_as_manager? %> -
- Manager Vetos: - <%= link_to admin_approve_vote_path(test_hash: @candidate.test_hash), remote: true do %> - - <%= @candidate.approved? ? "Requested" : "Request Interview" %> - - <% end %> - <%= link_to admin_decline_vote_path(test_hash: @candidate.test_hash), remote: true do %> - - <%= @candidate.declined? ? "Declined" : "Decline Interview" %> - +
+ <%= form_tag admin_interview_path(test_hash: @candidate.test_hash) do %> + Interview: + + <%= radio_button_tag :review_status, :approved, checked = @candidate.approved? %> + <%= label_tag :review_status_approved, "Yes" %> + + <%= radio_button_tag :review_status, :declined, checked = @candidate.declined? %> + <%= label_tag :review_status_declined, "No" %> + +
+ Review comments for recruiter + <%= text_area_tag :review_comments, @candidate.review_comments %> + <%= submit_tag 'Send Request' %> +
<% end %>
- <% else %> Candidate Interview Status: <%= @candidate.review_status %> + <% unless @candidate.review_status.blank? %> +
Review Status Comments:
+
<%= @candidate.review_comments %>
+ <% end %> <% end %> diff --git a/app/views/admin/result/index.html.erb b/app/views/admin/result/index.html.erb index 79ed6de..59ff4d2 100644 --- a/app/views/admin/result/index.html.erb +++ b/app/views/admin/result/index.html.erb @@ -5,19 +5,24 @@
- - + + + - + + <% @candidates.each do |candidate| %> - + + - + + <% end %>
Test IDExperience<%= sortable "test_hash", "Test ID" %><%= sortable "name" %><%= sortable "project", "Client/Project" %> RecruiterInterview Request<%= sortable "completed_at", "Submitted on" %>Interview?
<%= link_to candidate.test_hash, admin_result_path(candidate.test_hash) %><%= candidate.experience %> years<%= candidate.name if !candidate.pending? %><%= candidate.project %> <%= mail_to(candidate.recruiter.email) %><%= candidate.review_status unless candidate.pending? %><%= candidate.completed_at.strftime('%D') unless candidate.completed_at.nil? %><%= candidate.interview? %>
+ <%= paginate @candidates %>
diff --git a/app/views/admin/result/view.html.erb b/app/views/admin/result/view.html.erb index 61863fa..ab89ea6 100644 --- a/app/views/admin/result/view.html.erb +++ b/app/views/admin/result/view.html.erb @@ -2,41 +2,65 @@ content_for :title, "Quiz Review - Skills Assessment Admin" %> -
-

Quiz Review

+
+
+

Quiz Review

-
-
- Test ID: <%= @candidate.test_hash %>
- Years of Experience: <%= @candidate.experience %>
- Recruiter Email: <%= mail_to @candidate.recruiter.name, @candidate.recruiter.email %>
+
+
+ <% unless @candidate.pending? %> + Name: <%= @candidate.name %>
+ <% end %> + Test ID: <%= @candidate.test_hash %>
+ Years of Experience: <%= @candidate.experience %>
+ Client/Project: <%= @candidate.project %>
+ Position Type: <%= @candidate.position %>
+ Skill Needs: <%= @candidate.skill_needs %>
+ Recruiter Email: <%= mail_to @candidate.recruiter.name, @candidate.recruiter.email %>
+
-
<%= render partial: 'voting' %>
+ <% @quiz.each do |question| %> + <%= form_for(:answer, url: '#never-post', html:{id: 'summary-form'}) do |form| %> +
+
+
+

<%= question.question %>

+
+
+ +
+ <% if question.attachment.present? %> + <%= image_tag question.attachment %> + <% end %> +
+ <%= render partial: "quiz/#{question.input_type}", locals: {question: question, answer: question.answer, form: form} %> +
+
+
+ <% end #form_tag %> + <% end #questions loop %> + + <%= link_to(admin_results_path, { class: 'secondary-btn' }) do %> + + <% end %>
- <% @quiz.each do |question| %> - <%= form_for(:answer, url: '#never-post', html:{id: 'summary-form'}) do |form| %> -
-
-
-

<%= question.question %>

-
-
+
+
+

Voting

+
+
<%= render partial: 'voting' %>
+
+
-
- <% if question.attachment.present? %> - <%= image_tag question.attachment %> - <% end %> -
- <%= render partial: "quiz/#{question.input_type}", locals: {question: question, answer: question.answer, form: form} %> -
-
-
- <% end #form_tag %> - <% end #questions loop %> - - <%= link_to(admin_results_path, { class: 'secondary-btn' }) do %> - - <% end %> -
+
+

Comments

+ <% if policy(QuizComment).new? %> + <%= render partial: 'comment_form', locals: {comment: @comment, test_hash: @candidate.test_hash } %> + <% end %> + <%= render partial: 'comment', collection: @comments, locals: { test_hash: @candidate.test_hash } %> + Back to top +
+
+
diff --git a/app/views/admin/user/_table_list.html.erb b/app/views/admin/user/_table_list.html.erb index 9b8ef8f..63204eb 100644 --- a/app/views/admin/user/_table_list.html.erb +++ b/app/views/admin/user/_table_list.html.erb @@ -1,8 +1,8 @@ - - - + + + diff --git a/app/views/admin/vote/down.json.erb b/app/views/admin/vote/down.json.erb new file mode 100644 index 0000000..fe06c20 --- /dev/null +++ b/app/views/admin/vote/down.json.erb @@ -0,0 +1,6 @@ +{ + "message" : "Vote Counted", + "upCount" : <%= @candidate.votes.yea.count %>, + "downCount" : <%= @candidate.votes.nay.count %>, + "myVote" : "nay" +} diff --git a/app/views/admin/vote/up.json.erb b/app/views/admin/vote/up.json.erb new file mode 100644 index 0000000..605d7e1 --- /dev/null +++ b/app/views/admin/vote/up.json.erb @@ -0,0 +1,6 @@ +{ + "message" : "Vote Counted", + "upCount" : <%= @candidate.votes.yea.count %>, + "downCount" : <%= @candidate.votes.nay.count %>, + "myVote" : "yea" +} diff --git a/app/views/layouts/mailer.html.inky b/app/views/layouts/mailer.html.inky index 12f03c3..9761da3 100644 --- a/app/views/layouts/mailer.html.inky +++ b/app/views/layouts/mailer.html.inky @@ -23,7 +23,7 @@
UserEmailRole<%= sortable "name", "User" %><%= sortable "email" %><%= sortable "role" %>