codecombat/app/views/EmployersView.coffee

348 lines
14 KiB
CoffeeScript
Raw Normal View History

RootView = require 'views/kinds/RootView'
2014-01-14 01:29:58 -05:00
template = require 'templates/employers'
User = require 'models/User'
2014-04-24 20:36:07 -04:00
{me} = require 'lib/auth'
CocoCollection = require 'collections/CocoCollection'
EmployerSignupModal = require 'views/modal/EmployerSignupModal'
class CandidatesCollection extends CocoCollection
url: '/db/user/x/candidates'
model: User
module.exports = class EmployersView extends RootView
2014-06-30 22:16:26 -04:00
id: 'employers-view'
2014-01-14 01:29:58 -05:00
template: template
2014-04-07 18:21:05 -04:00
events:
2014-07-21 19:10:02 -04:00
'click #candidate-table': 'onCandidateClicked'
2014-07-07 23:01:59 -04:00
'click #logout-link': 'logoutAccount'
'change #filters input': 'onFilterChanged'
2014-07-03 14:39:44 -04:00
'change #select_all_checkbox': 'handleSelectAllChange'
2014-07-03 17:40:39 -04:00
'click .get-started-button': 'openSignupModal'
'click .navbar-brand': 'restoreBodyColor'
2014-08-31 18:08:52 -04:00
'click #login-link': 'onClickAuthButton'
2014-07-03 19:30:27 -04:00
'click #filter-link': 'swapFolderIcon'
2014-07-21 19:10:02 -04:00
'click #create-alert-button': 'createFilterAlert'
'click .deletion-col': 'deleteFilterAlert'
2014-04-07 18:21:05 -04:00
constructor: (options) ->
super options
2014-08-26 20:34:00 -04:00
@candidates = @supermodel.loadCollection(new CandidatesCollection(), 'candidates').model
@setFilterDefaults()
2014-07-04 10:36:23 -04:00
2014-08-26 20:34:00 -04:00
onLoaded: ->
super()
@setUpScrolling()
afterRender: ->
super()
@sortTable() if @candidates.models.length
2014-07-21 19:10:02 -04:00
@renderSavedFilters()
afterInsert: ->
super()
_.delay @checkForEmployerSignupHash, 500
2014-07-03 17:40:39 -04:00
#fairly hacky, change this in the future
2014-07-04 10:36:23 -04:00
@originalBackgroundColor = $('body').css 'background-color'
$('body').css 'background-color', '#B4B4B4'
2014-07-03 17:40:39 -04:00
restoreBodyColor: ->
2014-07-04 10:36:23 -04:00
$('body').css 'background-color', @originalBackgroundColor
2014-07-03 19:30:27 -04:00
swapFolderIcon: ->
2014-07-04 10:36:23 -04:00
$('#folder-icon').toggleClass('glyphicon-folder-close').toggleClass('glyphicon-folder-open')
2014-08-26 20:34:00 -04:00
2014-07-03 14:39:44 -04:00
onFilterChanged: ->
@resetFilters()
that = @
2014-07-04 10:36:23 -04:00
$('#filters :input').each ->
input = $(this)
checked = input.prop 'checked'
name = input.attr 'name'
2014-07-03 14:39:44 -04:00
value = input.val()
2014-07-04 10:36:23 -04:00
if name is 'phoneScreenFilter'
2014-07-03 14:39:44 -04:00
value = JSON.parse(input.prop 'value')
if checked
2014-07-03 14:39:44 -04:00
that.filters[name] = _.union that.filters[name], [value]
else
that.filters[name] = _.difference that.filters[name], [value]
2014-07-04 10:36:23 -04:00
2014-07-03 16:59:10 -04:00
for filterName, filterValues of @filters
if filterValues.length is 0
@filters[filterName] = @defaultFilters[filterName]
2014-07-08 14:19:14 -04:00
@applyFilters()
2014-07-04 10:36:23 -04:00
2014-07-03 17:40:39 -04:00
openSignupModal: ->
@openModalView new EmployerSignupModal
2014-08-26 20:34:00 -04:00
2014-07-03 14:39:44 -04:00
handleSelectAllChange: (e) ->
checkedState = e.currentTarget.checked
2014-07-04 10:36:23 -04:00
$('#filters :input').each ->
2014-07-03 14:39:44 -04:00
$(this).prop 'checked', checkedState
@onFilterChanged()
2014-07-04 10:36:23 -04:00
resetFilters: ->
for filterName, filterValues of @filters
@filters[filterName] = []
2014-07-04 10:36:23 -04:00
2014-07-02 19:47:02 -04:00
applyFilters: ->
candidateList = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
candidateList = _.filter candidateList, (c) -> c.get('jobProfileApproved')
2014-07-04 10:36:23 -04:00
2014-07-03 14:39:44 -04:00
filteredCandidates = candidateList
for filterName, filterValues of @filters
2014-07-04 10:36:23 -04:00
if filterName is 'visa'
2014-07-03 14:39:44 -04:00
filteredCandidates = _.difference filteredCandidates, _.filter(filteredCandidates, (c) ->
fieldValue = c.get('jobProfile').visa
2014-07-03 14:39:44 -04:00
return not (_.contains filterValues, fieldValue)
)
else
2014-07-03 14:39:44 -04:00
filteredCandidates = _.difference filteredCandidates, _.filter(filteredCandidates, (c) ->
unless c.get('jobProfile').curated then return true
fieldValue = c.get('jobProfile').curated?[filterName]
2014-07-03 14:39:44 -04:00
return not (_.contains filterValues, fieldValue)
)
2014-07-02 19:47:02 -04:00
candidateIDsToShow = _.pluck filteredCandidates, 'id'
2014-07-04 10:36:23 -04:00
$('#candidate-table tr').each -> $(this).hide()
2014-07-02 19:47:02 -04:00
candidateIDsToShow.forEach (id) ->
$("[data-candidate-id=#{id}]").show()
2014-07-04 10:36:23 -04:00
$('#results').text(candidateIDsToShow.length + ' results')
return filteredCandidates
2014-08-26 20:34:00 -04:00
setFilterDefaults: ->
2014-07-04 10:36:23 -04:00
@filters =
2014-07-03 14:39:44 -04:00
phoneScreenFilter: [true, false]
visa: ['Authorized to work in the US', 'Need visa sponsorship']
schoolFilter: ['Top School', 'Other']
locationFilter: ['Bay Area', 'New York', 'Other US', 'International']
2014-07-08 00:04:12 -04:00
roleFilter: ['Web Developer', 'Software Developer', 'Mobile Developer']
seniorityFilter: ['College Student', 'Recent Grad', 'Junior', 'Senior']
2014-07-03 16:59:10 -04:00
@defaultFilters = _.cloneDeep @filters
2014-07-04 10:36:23 -04:00
candidatesInFilter: (filterName, filterValue) =>
candidates = @getActiveAndApprovedCandidates()
if filterName and filterValue
if filterName is 'visa'
return (_.filter candidates, (c) -> c.get('jobProfile').visa is filterValue).length
else
return (_.filter candidates, (c) -> c.get('jobProfile').curated?[filterName] is filterValue).length
else
return Math.floor(Math.random() * 500)
2014-08-26 20:34:00 -04:00
2014-07-21 19:10:02 -04:00
createFilterAlert: ->
currentFilterSet = _.cloneDeep @filters
currentSavedFilters = _.cloneDeep me.get('savedEmployerFilterAlerts') ? []
currentSavedFilters.push currentFilterSet
@patchEmployerFilterAlerts currentSavedFilters, @renderSavedFilters
2014-08-26 20:34:00 -04:00
2014-07-21 19:10:02 -04:00
deleteFilterAlert: (e) ->
index = $(e.target).closest('tbody tr').data('filter-index')
currentSavedFilters = me.get('savedEmployerFilterAlerts')
currentSavedFilters.splice(index,1)
@patchEmployerFilterAlerts currentSavedFilters, @renderSavedFilters
2014-08-26 20:34:00 -04:00
2014-07-21 19:10:02 -04:00
patchEmployerFilterAlerts: (newFilters, cb) ->
me.set('savedEmployerFilterAlerts',newFilters)
unless me.isValid()
alert("There was an error setting the filter(me.isValid() returned false.) Reverting! Please notify team@codecombat.com.")
me.set 'savedEmployerFilterAlerts', me.previous('savedEmployerFilterAlerts')
else
triggerErrorAlert = -> alert("There was an error saving your filter alert! Please notify team@codecombat.com.")
res = me.save {"savedEmployerFilterAlerts": newFilters}, {patch: true, success: cb, error: triggerErrorAlert}
2014-08-26 20:34:00 -04:00
2014-07-21 19:10:02 -04:00
renderSavedFilters: =>
savedFilters = me.get('savedEmployerFilterAlerts')
unless savedFilters?.length then return $("#saved-filter-table").hide()
$("#saved-filter-table").show()
$("#saved-filter-table").find("tbody tr").remove()
for filter, index in savedFilters
$("#saved-filter-table tbody").append("""<tr data-filter-index="#{index}"><td>#{@generateFilterAlertDescription(filter)}</td><td class="deletion-col"><a>✗</a></td></tr> """)
2014-08-26 20:34:00 -04:00
2014-07-21 19:10:02 -04:00
generateFilterAlertDescription: (filter) =>
descriptionString = ""
for key in _.keys(filter).sort()
value = filter[key]
if key is "filterActive" or _.without(@defaultFilters[key],value...).length is 0 then continue
if descriptionString.length then descriptionString += ", "
descriptionString += value.join(", ")
if descriptionString.length is 0 then descriptionString = "Any new candidate"
return descriptionString
2014-08-26 20:34:00 -04:00
getActiveAndApprovedCandidates: =>
candidates = _.filter @candidates.models, (c) -> c.get('jobProfile').active
return _.filter candidates, (c) -> c.get('jobProfileApproved')
getRenderData: ->
ctx = super()
ctx.isEmployer = @isEmployer()
#If you change the candidates displayed, change candidatesInFilter()
ctx.candidates = _.sortBy @candidates.models, (c) -> -1 * c.get('jobProfile').experience
ctx.candidates = _.sortBy ctx.candidates, (c) -> not c.get('jobProfile').curated?
ctx.candidates = _.sortBy ctx.candidates, (c) -> c.get('jobProfile').curated?.featured
ctx.activeCandidates = _.filter ctx.candidates, (c) -> c.get('jobProfile').active
ctx.inactiveCandidates = _.reject ctx.candidates, (c) -> c.get('jobProfile').active
ctx.featuredCandidates = _.filter ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
2014-07-03 16:59:10 -04:00
unless @isEmployer() or me.isAdmin()
ctx.featuredCandidates = _.filter ctx.featuredCandidates, (c) -> c.get('jobProfile').curated
ctx.featuredCandidates = ctx.featuredCandidates.slice(0,7)
if me.isAdmin()
ctx.featuredCandidates = ctx.candidates
ctx.candidatesInFilter = @candidatesInFilter
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.moment = moment
ctx._ = _
2014-07-03 19:30:27 -04:00
ctx.numberOfCandidates = ctx.featuredCandidates.length
ctx
2014-04-25 11:47:31 -04:00
isEmployer: -> 'employer' in me.get('permissions', true)
2014-08-26 20:34:00 -04:00
setUpScrolling: =>
2014-06-30 22:16:26 -04:00
$('.nano').nanoScroller()
2014-07-03 19:40:00 -04:00
#if window.history?.state?.lastViewedCandidateID
2014-07-04 10:36:23 -04:00
# $('.nano').nanoScroller({scrollTo: $('#' + window.history.state.lastViewedCandidateID)})
2014-07-03 19:40:00 -04:00
#else if window.location.hash.length is 25
2014-07-04 10:36:23 -04:00
# $('.nano').nanoScroller({scrollTo: $(window.location.hash)})
checkForEmployerSignupHash: =>
if window.location.hash is '#employerSignupLoggingIn' and not ('employer' in me.get('permissions', true)) and not me.isAdmin()
@openModalView new EmployerSignupModal
2014-06-30 22:16:26 -04:00
window.location.hash = ''
sortTable: ->
# http://mottie.github.io/tablesorter/docs/example-widget-bootstrap-theme.html
$.extend $.tablesorter.themes.bootstrap,
# these classes are added to the table. To see other table classes available,
# look here: http://twitter.github.com/bootstrap/base-css.html#tables
2014-06-30 22:16:26 -04:00
table: 'table table-bordered'
caption: 'caption'
header: 'bootstrap-header' # give the header a gradient background
footerRow: ''
footerCells: ''
icons: '' # add 'icon-white' to make them white; this icon class is added to the <i> in the header
sortNone: 'bootstrap-icon-unsorted'
sortAsc: 'icon-chevron-up' # glyphicon glyphicon-chevron-up' # we are still using v2 icons
sortDesc: 'icon-chevron-down' # glyphicon-chevron-down' # we are still using v2 icons
active: '' # applied when column is sorted
hover: '' # use custom css here - bootstrap class may not override it
filterRow: '' # filter row class
even: '' # odd row zebra striping
odd: '' # even row zebra striping
2014-04-24 14:08:42 -04:00
# e = exact text from cell
# n = normalized value returned by the column parser
# f = search filter input value
# i = column index
# $r = ???
filterSelectExactMatch = (e, n, f, i, $r) -> e is f
# call the tablesorter plugin and apply the uitheme widget
2014-06-30 22:16:26 -04:00
@$el.find('.tablesorter').tablesorter
theme: 'bootstrap'
widthFixed: true
2014-06-30 22:16:26 -04:00
headerTemplate: '{content} {icon}'
2014-04-24 14:08:42 -04:00
textSorter:
6: (a, b, direction, column, table) ->
days = []
for s in [a, b]
n = parseInt s
n = 0 unless _.isNumber n
n = 1 if /^a/.test s
2014-04-24 14:08:42 -04:00
for [duration, factor] in [
2014-06-30 22:16:26 -04:00
[/second/i, 1/(86400*1000)]
[/minute/i, 1/1440]
[/hour/i, 1/24]
2014-04-24 14:08:42 -04:00
[/week/i, 7]
[/month/i, 30.42]
[/year/i, 365.2425]
]
if duration.test s
n *= factor
break
if /^in /i.test s
n *= -1
days.push n
days[0] - days[1]
sortList: if @isEmployer() or me.isAdmin() then [[6, 0]] else [[0, 1]]
# widget code contained in the jquery.tablesorter.widgets.js file
# use the zebra stripe widget if you plan on hiding any rows (filter widget)
2014-06-30 22:16:26 -04:00
widgets: ['uitheme', 'zebra', 'filter']
widgetOptions:
# using the default zebra striping class name, so it actually isn't included in the theme variable above
# this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden
2014-06-30 22:16:26 -04:00
zebra: ['even', 'odd']
2014-04-24 14:08:42 -04:00
# extra css class applied to the table row containing the filters & the inputs within that row
2014-06-30 22:16:26 -04:00
filter_cssFilter: ''
2014-04-24 14:08:42 -04:00
2014-06-30 22:16:26 -04:00
# If there are child rows in the table (rows with class name from 'cssChildRow' option)
2014-04-24 14:08:42 -04:00
# and this option is true and a match is found anywhere in the child row, then it will make that row
# visible; default is false
filter_childRows: false
# if true, filters are collapsed initially, but can be revealed by hovering over the grey bar immediately
# below the header row. Additionally, tabbing through the document will open the filter row when an input gets focus
filter_hideFilters: false
# Set this option to false to make the searches case sensitive
filter_ignoreCase: true
# jQuery selector string of an element used to reset the filters
2014-06-30 22:16:26 -04:00
filter_reset: '.reset'
2014-04-24 14:08:42 -04:00
# Use the $.tablesorter.storage utility to save the most recent filters
filter_saveFilters: true
# Delay in milliseconds before the filter widget starts searching; This option prevents searching for
# every character while typing and should make searching large tables faster.
filter_searchDelay: 150
# Set this option to true to use the filter to find text from the start of the column
2014-06-30 22:16:26 -04:00
# So typing in 'a' will find 'albert' but not 'frank', both have a's; default is false
2014-04-24 14:08:42 -04:00
filter_startsWith: false
filter_functions:
2:
2014-06-30 22:16:26 -04:00
'Full-time': filterSelectExactMatch
'Part-time': filterSelectExactMatch
'Contracting': filterSelectExactMatch
'Remote': filterSelectExactMatch
'Internship': filterSelectExactMatch
2014-04-24 14:08:42 -04:00
5:
2014-06-30 22:16:26 -04:00
'0-1': (e, n, f, i, $r) -> n <= 1
'2-5': (e, n, f, i, $r) -> 2 <= n <= 5
'6+': (e, n, f, i, $r) -> 6 <= n
2014-04-24 14:08:42 -04:00
6:
2014-06-30 22:16:26 -04:00
'Last day': (e, n, f, i, $r) ->
2014-04-24 14:08:42 -04:00
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 1
2014-06-30 22:16:26 -04:00
'Last week': (e, n, f, i, $r) ->
2014-04-24 14:08:42 -04:00
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 7
2014-06-30 22:16:26 -04:00
'Last 4 weeks': (e, n, f, i, $r) ->
2014-04-24 14:08:42 -04:00
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 28
2014-06-17 16:03:08 -04:00
8:
2014-06-30 22:16:26 -04:00
'': filterSelectExactMatch
'': filterSelectExactMatch
2014-08-26 20:34:00 -04:00
logoutAccount: ->
window.location.hash = ''
super()
2014-08-26 20:34:00 -04:00
2014-04-07 18:21:05 -04:00
onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id')
if id and (@isEmployer() or me.isAdmin())
if window.history
oldState = _.cloneDeep window.history.state ? {}
2014-06-30 22:16:26 -04:00
oldState['lastViewedCandidateID'] = id
window.history.replaceState(oldState, '')
else
window.location.hash = id
2014-04-07 18:21:05 -04:00
url = "/account/profile/#{id}"
2014-06-30 22:16:26 -04:00
window.open url, '_blank'
2014-04-07 18:21:05 -04:00
else
@openModalView new EmployerSignupModal