This commit is contained in:
Nick Winter 2014-07-17 15:50:38 -07:00
commit 8813a7f545
5 changed files with 394 additions and 1 deletions
app
server/routes

View file

@ -0,0 +1,74 @@
#admin-candidates-view
h1, h2, h3
font: Arial
.see-candidates-header
margin: 30px
text-align: center
#see-candidates
cursor: pointer
.employer_icon
width: 125px
float: left
margin: 0px 15px 15px 0px
.information_row
height: 150px
padding-right: 15px
#leftside
width: 500px
float: left
#rightside
width: 500px
float: left
.tablesorter
//img
// display: none
.tablesorter-header
cursor: pointer
&:hover
color: black
&:first-child
// Make sure that "Developer #56" doesn't wrap onto second row
min-width: 110px
.tablesorter-headerAsc
background-color: #cfc
.tablesorter-headerDesc
background-color: #ccf
tr
cursor: pointer
tr.expired
opacity: 0.5
code
background-color: rgb(220, 220, 220)
color: #555
margin: 2px 0
display: inline-block
text-transform: lowercase
td:nth-child(3) select
min-width: 100px
td:nth-child(6) select
min-width: 50px
td:nth-child(7) select
min-width: 100px
#employers-view, #profile-view.viewed-by-employer
#outer-content-wrapper, #intermediate-content-wrapper, #inner-content-wrapper
background: #949494
.main-content-area
background-color: #EAEAEA

View file

@ -25,6 +25,8 @@ block content
a(href="/admin/level_sessions", data-i18n="admin.av_entities_active_instances_url") Active Instances
li
a(href="/admin/employer_list", data-i18n="admin.av_entities_employer_list_url") Employer List
li
a(href="/admin/candidates") Candidate List
h4(data-i18n="admin.av_other_sub_title") Other

View file

@ -0,0 +1,110 @@
extends /templates/base
block content
if !me.isAdmin()
h1 Admins Only
if me.isAdmin()
h1(data-i18n="employers.want_to_hire_our_players") Hire CodeCombat Players
if !isEmployer && !me.isAdmin()
div#info_wrapper
div#leftside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon1.png")
h2(data-i18n="employers.what") What is CodeCombat?
p(data-i18n="employers.what_blurb") CodeCombat is a multiplayer browser programming game. Players write code to control their forces in battle against other developers. We support JavaScript, Python, Lua, Clojure, CoffeeScript, and Io.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon3.png")
h2(data-i18n="employers.who") Who Are the Players?
p(data-i18n="employers.who_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon5.png")
h2(data-i18n="employers.cost") Who Are the Players?
p(data-i18n="employers.cost_blurb") CodeCombateers are CTOs, VPs of Engineering, and graduates of top 20 engineering schools. No junior developers here. Our players enjoy playing with code and solving problems.
div#rightside
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon2.png")
h2(data-i18n="employers.how") How Much Do we Charge?
p(data-i18n="employers.how_blurb") We charge 15% of first year's salary and offer a 100% money back guarantee for 90 days. We don't charge for candidates who are already actively being interviewed at your company.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon4.png")
h2(data-i18n="employers.why") Why Hire Through Us?
p
span(data-i18n="employers.why_blurb_1") We will save you time. Every CodeCombateer we feaure is
strong(data-i18n="employers.why_blurb_2") looking for work
span(data-i18n="employers.why_blurb_3") , has
strong(data-i18n="employers.why_blurb_4") demonstrated top notch technical skills
span(data-i18n="employers.why_blurb_5") , and has been
strong(data-i18n="employers.why_blurb_6") personally screened by us
span(data-i18n="employers.why_blurb_7") . Stop screening and start hiring.
div.information_row
img(class="employer_icon" src="/images/pages/employer/employer_icon6.png")
h2(data-i18n="employers.response") What's the Response Rate?
p(data-i18n="employers.response_blurb") Almost every developer you contact on CodeCombat will respond to inquires whether or not they want to interivew. If you would like help finding a candidate for your role, we can make recommendations.
if candidates.length
ul.nav.nav-pills
li.active
a(href="#featured-candidates", data-toggle="tab")
span(data-i18n="employers.featured_developers") Featured Developers
| (#{featuredCandidates.length})
if otherCandidates.length
li
a(href="#other-candidates", data-toggle="tab")
span(data-i18n="employers.other_developers") Other Developers
| (#{otherCandidates.length})
if me.isAdmin() && inactiveCandidates.length
li
a(href="#inactive-candidates", data-toggle="tab")
span(data-i18n="employers.inactive_developers") Inactive Developers
| (#{inactiveCandidates.length})
div.tab-content
for area, tabIndex in [{id: "featured-candidates", candidates: featuredCandidates}, {id: "other-candidates", candidates: otherCandidates}, {id: "inactive-candidates", candidates: inactiveCandidates}]
div(class="tab-pane well" + (tabIndex ? "" : " active"), id=area.id)
table.table.table-condensed.table-hover.table-responsive.tablesorter
thead
tr
th(data-i18n="general.name") Name
th(data-i18n="employers.candidate_location") Location
th(data-i18n="employers.candidate_looking_for") Looking For
th(data-i18n="employers.candidate_role") Role
th(data-i18n="employers.candidate_top_skills") Top Skills
th(data-i18n="employers.candidate_years_experience") Yrs Exp
th(data-i18n="employers.candidate_last_updated") Last Updated
if me.isAdmin()
th(data-i18n="employers.candidate_who") Who
if me.isAdmin() && area.id == 'inactive-candidates'
th ✓?
tbody
for candidate, index in area.candidates
- var profile = candidate.get('jobProfile');
- var authorized = candidate.id; // If we have the id, then we are authorized.
- var profileAge = (new Date() - new Date(profile.updated)) / 86400 / 1000;
- var expired = profileAge > 2 * 30.4;
tr(data-candidate-id=candidate.id, id=candidate.id, class=expired ? "expired" : "")
td
if authorized
img(src=candidate.getPhotoURL(50), alt=profile.name, title=profile.name, height=50)
if profile.name
p= profile.name
else if me.isAdmin()
p (#{candidate.get('name')})
else
img(src="/images/pages/contribute/archmage.png", alt="", title="Sign up as an employer to see our candidates", width=50)
p Developer ##{index + 1 + (area.id == 'featured-candidates' ? 0 : featuredCandidates.length)}
if profile.country == 'USA'
td= profile.city
else
td= profile.country
td= profile.lookingFor
td= profile.jobTitle
td
each skill in profile.skills
code= skill
span
td= profile.experience
td(data-profile-age=profileAge)= moment(profile.updated).fromNow()
if me.isAdmin()
td= remarks[candidate.id] ? remarks[candidate.id].get('contactName') : ''
if me.isAdmin() && area.id == 'inactive-candidates'
if candidate.get('jobProfileApproved')
td ✓
else
td ✗

View file

@ -0,0 +1,207 @@
View = require 'views/kinds/RootView'
template = require 'templates/admin/candidates'
app = require 'application'
User = require 'models/User'
UserRemark = require 'models/UserRemark'
{me} = require 'lib/auth'
CocoCollection = require 'collections/CocoCollection'
EmployerSignupView = require 'views/modal/employer_signup_modal'
class CandidatesCollection extends CocoCollection
url: '/db/user/x/candidates'
model: User
class UserRemarksCollection extends CocoCollection
url: '/db/user.remark?project=contact,contactName,user'
model: UserRemark
module.exports = class EmployersView extends View
id: "admin-candidates-view"
template: template
events:
'click tbody tr': 'onCandidateClicked'
constructor: (options) ->
super options
@getCandidates()
afterRender: ->
super()
@sortTable() if @candidates.models.length
afterInsert: ->
super()
_.delay @checkForEmployerSignupHash, 500
getRenderData: ->
ctx = super()
ctx.isEmployer = @isEmployer()
ctx.candidates = _.sortBy @candidates.models, (c) -> c.get('jobProfile').updated
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')
ctx.otherCandidates = _.reject ctx.activeCandidates, (c) -> c.get('jobProfileApproved')
ctx.remarks = {}
ctx.remarks[remark.get('user')] = remark for remark in @remarks.models
ctx.moment = moment
ctx._ = _
ctx
isEmployer: ->
userPermissions = me.get('permissions') ? []
_.contains userPermissions, "employer"
getCandidates: ->
@candidates = new CandidatesCollection()
@candidates.fetch()
@remarks = new UserRemarksCollection()
@remarks.fetch()
# Re-render when we have fetched them, but don't wait and show a progress bar while loading.
@listenToOnce @candidates, 'all', @renderCandidatesAndSetupScrolling
@listenToOnce @remarks, 'all', @renderCandidatesAndSetupScrolling
renderCandidatesAndSetupScrolling: =>
@render()
$(".nano").nanoScroller()
if window.history?.state?.lastViewedCandidateID
$(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
else if window.location.hash.length is 25
$(".nano").nanoScroller({scrollTo:$(window.location.hash)})
checkForEmployerSignupHash: =>
if window.location.hash is "#employerSignupLoggingIn" and not ("employer" in me.get("permissions"))
@openModalView application.router.getView("modal/employer_signup","_modal")
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
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
# 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
@$el.find(".tablesorter").tablesorter
theme: "bootstrap"
widthFixed: true
headerTemplate: "{content} {icon}"
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
for [duration, factor] in [
[/second/i, 1 / (86400 * 1000)]
[/minute/i, 1 / 1440]
[/hour/i, 1 / 24]
[/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)
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
zebra: ["even", "odd"]
# extra css class applied to the table row containing the filters & the inputs within that row
filter_cssFilter: ""
# If there are child rows in the table (rows with class name from "cssChildRow" option)
# 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
filter_reset: ".reset"
# 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
# So typing in "a" will find "albert" but not "frank", both have a's; default is false
filter_startsWith: false
filter_functions:
2:
"Full-time": filterSelectExactMatch
"Part-time": filterSelectExactMatch
"Contracting": filterSelectExactMatch
"Remote": filterSelectExactMatch
"Internship": filterSelectExactMatch
5:
"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
6:
"Last day": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 1
"Last week": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 7
"Last 4 weeks": (e, n, f, i, $r) ->
days = parseFloat $($r.find('td')[i]).data('profile-age')
days <= 28
8:
"": filterSelectExactMatch
"": filterSelectExactMatch
onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id')
if id
if window.history
oldState = _.cloneDeep window.history.state ? {}
oldState["lastViewedCandidateID"] = id
window.history.replaceState(oldState,"")
else
window.location.hash = id
url = "/account/profile/#{id}"
window.open url,"_blank"
else
@openModalView new EmployerSignupView

View file

@ -327,7 +327,7 @@ employerMatchingCandidateNotificationTask = ->
###
### End Employer Matching Candidate Notification Email ###
### Ladder Update Email ###
### Employer ignore ###
DEBUGGING = false
LADDER_PREGAME_INTERVAL = 2 * 3600 * 1000 # Send emails two hours before players last submitted.
getTimeFromDaysAgo = (now, daysAgo) ->