2016-03-30 16:57:19 -04:00
RootView = require ' views/core/RootView '
2016-04-19 16:44:48 -04:00
State = require ' models/State '
2016-03-30 16:57:19 -04:00
template = require ' templates/courses/teacher-class-view '
helper = require ' lib/coursesHelper '
ClassroomSettingsModal = require ' views/courses/ClassroomSettingsModal '
InviteToClassroomModal = require ' views/courses/InviteToClassroomModal '
ActivateLicensesModal = require ' views/courses/ActivateLicensesModal '
2016-05-11 17:39:26 -04:00
EditStudentModal = require ' views/teachers/EditStudentModal '
2016-03-30 19:20:37 -04:00
RemoveStudentModal = require ' views/courses/RemoveStudentModal '
2016-03-30 16:57:19 -04:00
2016-06-09 17:44:43 -04:00
Campaigns = require ' collections/Campaigns '
2016-03-30 16:57:19 -04:00
Classroom = require ' models/Classroom '
Classrooms = require ' collections/Classrooms '
2016-05-05 12:54:21 -04:00
Levels = require ' collections/Levels '
2016-03-30 16:57:19 -04:00
LevelSessions = require ' collections/LevelSessions '
User = require ' models/User '
Users = require ' collections/Users '
2016-04-19 16:44:48 -04:00
Course = require ' models/Course '
2016-03-30 16:57:19 -04:00
Courses = require ' collections/Courses '
CourseInstance = require ' models/CourseInstance '
CourseInstances = require ' collections/CourseInstances '
module.exports = class TeacherClassView extends RootView
id: ' teacher-class-view '
template: template
2016-07-17 04:12:58 -04:00
helper: helper
2016-05-05 12:54:21 -04:00
2016-03-30 16:57:19 -04:00
events:
2016-05-09 18:16:54 -04:00
' click .nav-tabs a ' : ' onClickNavTabLink '
2016-04-19 16:44:48 -04:00
' click .unarchive-btn ' : ' onClickUnarchive '
2016-03-30 16:57:19 -04:00
' click .edit-classroom ' : ' onClickEditClassroom '
' click .add-students-btn ' : ' onClickAddStudents '
2016-05-11 17:39:26 -04:00
' click .edit-student-link ' : ' onClickEditStudentLink '
2016-05-09 18:16:54 -04:00
' click .sort-button ' : ' onClickSortButton '
' click # copy-url-btn ' : ' onClickCopyURLButton '
' click # copy-code-btn ' : ' onClickCopyCodeButton '
2016-03-30 19:20:37 -04:00
' click .remove-student-link ' : ' onClickRemoveStudentLink '
2016-05-09 18:16:54 -04:00
' click .assign-student-button ' : ' onClickAssignStudentButton '
' click .enroll-student-button ' : ' onClickEnrollStudentButton '
2016-03-30 16:57:19 -04:00
' click .assign-to-selected-students ' : ' onClickBulkAssign '
' click .enroll-selected-students ' : ' onClickBulkEnroll '
2016-05-05 12:54:21 -04:00
' click .export-student-progress-btn ' : ' onClickExportStudentProgress '
2016-03-30 16:57:19 -04:00
' click .select-all ' : ' onClickSelectAll '
' click .student-checkbox ' : ' onClickStudentCheckbox '
2016-05-09 18:16:54 -04:00
' keyup # student-search ' : ' onKeyPressStudentSearch '
2016-05-26 18:01:16 -04:00
' change .course-select, .bulk-course-select ' : ' onChangeCourseSelect '
2016-07-17 03:53:17 -04:00
2016-04-19 16:44:48 -04:00
getInitialState: ->
{
sortAttribute: ' name '
sortDirection: 1
2016-05-09 18:16:54 -04:00
activeTab: ' # ' + ( Backbone . history . getHash ( ) or ' students-tab ' )
2016-04-19 16:44:48 -04:00
students: new Users ( )
classCode: " "
joinURL: " "
errors:
assigningToNobody: false
assigningToUnenrolled: false
selectedCourse: undefined
2016-07-07 16:06:15 -04:00
checkboxStates: { }
2016-04-19 16:44:48 -04:00
classStats:
averagePlaytime: " "
totalPlaytime: " "
averageLevelsComplete: " "
totalLevelsComplete: " "
enrolledUsers: " "
}
2016-03-30 16:57:19 -04:00
2016-05-27 12:40:46 -04:00
getTitle: -> return @ classroom ? . get ( ' name ' )
2016-03-30 16:57:19 -04:00
initialize: (options, classroomID) ->
super ( options )
2016-04-19 16:44:48 -04:00
@singleStudentCourseProgressDotTemplate = require ' templates/teachers/hovers/progress-dot-single-student-course '
@singleStudentLevelProgressDotTemplate = require ' templates/teachers/hovers/progress-dot-single-student-level '
@allStudentsLevelProgressDotTemplate = require ' templates/teachers/hovers/progress-dot-all-students-single-level '
2016-08-19 18:48:45 -04:00
@urls = require ( ' core/urls ' )
2016-07-17 03:53:17 -04:00
2016-06-06 17:30:58 -04:00
@debouncedRender = _ . debounce @ render
2016-07-17 03:53:17 -04:00
2016-04-19 16:44:48 -04:00
@state = new State ( @ getInitialState ( ) )
2016-05-09 18:16:54 -04:00
@ updateHash @ state . get ( ' activeTab ' ) # TODO: Don't push to URL history (maybe don't use url fragment for default tab)
2016-07-17 03:53:17 -04:00
2016-03-30 16:57:19 -04:00
@classroom = new Classroom ( { _id: classroomID } )
2016-05-09 18:16:54 -04:00
@ supermodel . trackRequest @ classroom . fetch ( )
@onKeyPressStudentSearch = _ . debounce ( @ onKeyPressStudentSearch , 200 )
2016-07-17 03:53:17 -04:00
2016-04-19 16:44:48 -04:00
@students = new Users ( )
2016-03-30 16:57:19 -04:00
@ listenTo @ classroom , ' sync ' , ->
2016-04-07 17:55:42 -04:00
jqxhrs = @ students . fetchForClassroom ( @ classroom , removeDeleted: true )
2016-05-09 18:16:54 -04:00
@ supermodel . trackRequests jqxhrs
2016-07-17 03:53:17 -04:00
2016-03-30 16:57:19 -04:00
@classroom.sessions = new LevelSessions ( )
2016-03-30 19:20:37 -04:00
requests = @ classroom . sessions . fetchForAllClassroomMembers ( @ classroom )
@ supermodel . trackRequests ( requests )
2016-05-05 12:54:21 -04:00
2016-05-09 18:16:54 -04:00
@students.comparator = (student1, student2) =>
dir = @ state . get ( ' sortDirection ' )
value = @ state . get ( ' sortValue ' )
if value is ' name '
return ( if student1 . broadName ( ) . toLowerCase ( ) < student2 . broadName ( ) . toLowerCase ( ) then - dir else dir )
2016-07-17 03:53:17 -04:00
2016-05-09 18:16:54 -04:00
if value is ' progress '
# TODO: I would like for this to be in the Level model,
# but it doesn't know about its own courseNumber.
level1 = student1 . latestCompleteLevel
level2 = student2 . latestCompleteLevel
return - dir if not level1
return dir if not level2
return dir * ( level1 . courseNumber - level2 . courseNumber or level1 . levelNumber - level2 . levelNumber )
2016-07-17 03:53:17 -04:00
2016-05-09 18:16:54 -04:00
if value is ' status '
statusMap = { expired: 0 , ' not-enrolled ' : 1 , enrolled: 2 }
diff = statusMap [ student1 . prepaidStatus ( ) ] - statusMap [ student2 . prepaidStatus ( ) ]
return dir * diff if diff
return ( if student1 . broadName ( ) . toLowerCase ( ) < student2 . broadName ( ) . toLowerCase ( ) then - dir else dir )
2016-05-05 12:54:21 -04:00
2016-05-09 18:16:54 -04:00
@courses = new Courses ( )
@ supermodel . trackRequest @ courses . fetch ( )
2016-07-16 02:26:43 -04:00
2016-03-30 16:57:19 -04:00
@courseInstances = new CourseInstances ( )
2016-05-09 18:16:54 -04:00
@ supermodel . trackRequest @ courseInstances . fetchForClassroom ( classroomID )
2016-06-09 17:44:43 -04:00
2016-05-05 12:54:21 -04:00
@levels = new Levels ( )
2016-08-16 18:19:03 -04:00
@ supermodel . trackRequest @ levels . fetchForClassroom ( classroomID , { data: { project: ' original,concepts,primerLanguage,practice,shareable,i18n ' } } )
2016-07-16 02:26:43 -04:00
2016-04-19 16:44:48 -04:00
@ attachMediatorEvents ( )
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Loaded ' , category: ' Teachers ' , classroomID: @ classroom . id , [ ' Mixpanel ' ]
2016-05-27 12:40:46 -04:00
2016-04-19 16:44:48 -04:00
attachMediatorEvents: () ->
# Model/Collection events
@ listenTo @ classroom , ' sync change update ' , ->
classCode = @ classroom . get ( ' codeCamel ' ) or @ classroom . get ( ' code ' )
@ state . set {
classCode: classCode
joinURL: document . location . origin + " /courses?_cc= " + classCode
}
@ listenTo @ courses , ' sync change update ' , ->
@ setCourseMembers ( ) # Is this necessary?
@ state . set selectedCourse: @ courses . first ( ) unless @ state . get ( ' selectedCourse ' )
@ listenTo @ courseInstances , ' sync change update ' , ->
@ setCourseMembers ( )
@ listenTo @ courseInstances , ' add-members ' , ->
noty text: $ . i18n . t ( ' teacher.assigned ' ) , layout: ' center ' , type: ' information ' , killer: true , timeout: 5000
@ listenTo @ students , ' sync change update add remove reset ' , ->
# Set state/props of things that depend on students?
# Set specific parts of state based on the models, rather than just dumping the collection there?
@ calculateProgressAndLevels ( )
classStats = @ calculateClassStats ( )
@ state . set classStats: classStats if classStats
@ state . set students: @ students
2016-07-07 16:06:15 -04:00
checkboxStates = { }
for student in @ students . models
checkboxStates [ student . id ] = @ state . get ( ' checkboxStates ' ) [ student . id ] or false
@ state . set { checkboxStates }
2016-04-19 16:44:48 -04:00
@ listenTo @ students , ' sort ' , ->
@ state . set students: @ students
2016-05-26 18:01:16 -04:00
@ listenTo @ , ' course-select:change ' , ({ selectedCourse }) ->
@ state . set selectedCourse: selectedCourse
2016-04-19 16:44:48 -04:00
setCourseMembers: =>
for course in @ courses . models
course.instance = @ courseInstances . findWhere ( { courseID: course . id , classroomID: @ classroom . id } )
course.members = course . instance ? . get ( ' members ' ) or [ ]
null
2016-07-17 03:53:17 -04:00
2016-03-30 16:57:19 -04:00
onLoaded: ->
2016-04-19 16:44:48 -04:00
@ removeDeletedStudents ( ) # TODO: Move this to mediator listeners? For both classroom and students?
@ calculateProgressAndLevels ( )
2016-07-17 03:53:17 -04:00
2016-06-06 16:55:31 -04:00
# render callback setup
@ listenTo @ courseInstances , ' sync change update ' , @ debouncedRender
@ listenTo @ state , ' sync change ' , ->
if _ . isEmpty ( _ . omit ( @ state . changed , ' searchTerm ' ) )
@ renderSelectors ( ' # enrollment-status-table ' )
else
@ debouncedRender ( )
@ listenTo @ students , ' sort ' , @ debouncedRender
2016-04-19 16:44:48 -04:00
super ( )
2016-07-16 00:57:04 -04:00
2016-04-19 16:44:48 -04:00
afterRender: ->
super ( arguments . . . )
2016-07-16 00:57:04 -04:00
$ ( ' .progress-dot, .btn-view-project-level ' ) . each (i, el) ->
2016-04-19 16:44:48 -04:00
dot = $ ( el )
dot . tooltip ( {
html: true
container: dot
} ) . delegate ' .tooltip ' , ' mousemove ' , ->
dot . tooltip ( ' hide ' )
2016-07-16 00:57:04 -04:00
2016-04-19 16:44:48 -04:00
calculateProgressAndLevels: ->
return unless @ supermodel . progress is 1
# TODO: How to structure this in @state?
2016-03-30 16:57:19 -04:00
for student in @ students . models
# TODO: this is a weird hack
studentsStub = new Users ( [ student ] )
2016-04-13 12:54:24 -04:00
student.latestCompleteLevel = helper . calculateLatestComplete ( @ classroom , @ courses , @ courseInstances , studentsStub )
2016-07-17 03:53:17 -04:00
2016-04-19 16:44:48 -04:00
earliestIncompleteLevel = helper . calculateEarliestIncomplete ( @ classroom , @ courses , @ courseInstances , @ students )
latestCompleteLevel = helper . calculateLatestComplete ( @ classroom , @ courses , @ courseInstances , @ students )
2016-07-17 03:53:17 -04:00
2016-03-30 16:57:19 -04:00
classroomsStub = new Classrooms ( [ @ classroom ] )
2016-04-19 16:44:48 -04:00
progressData = helper . calculateAllProgress ( classroomsStub , @ courses , @ courseInstances , @ students )
# conceptData: helper.calculateConceptsCovered(classroomsStub, @courses, @campaigns, @courseInstances, @students)
2016-07-17 03:53:17 -04:00
2016-04-19 16:44:48 -04:00
@ state . set {
earliestIncompleteLevel
latestCompleteLevel
progressData
classStats: @ calculateClassStats ( )
}
2016-05-09 18:16:54 -04:00
onClickNavTabLink: (e) ->
e . preventDefault ( )
hash = $ ( e . target ) . closest ( ' a ' ) . attr ( ' href ' )
@ updateHash ( hash )
@ state . set activeTab: hash
2016-07-17 03:53:17 -04:00
2016-05-09 18:16:54 -04:00
updateHash: (hash) ->
return if application . testing
window . location.hash = hash
onClickCopyCodeButton: ->
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Copy Class Code ' , category: ' Teachers ' , classroomID: @ classroom . id , classCode: @ state . get ( ' classCode ' ) , [ ' Mixpanel ' ]
2016-04-19 16:44:48 -04:00
@ $ ( ' # join-code-input ' ) . val ( @ state . get ( ' classCode ' ) ) . select ( )
2016-03-30 16:57:19 -04:00
@ tryCopy ( )
2016-05-05 12:54:21 -04:00
2016-05-09 18:16:54 -04:00
onClickCopyURLButton: ->
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Copy Class URL ' , category: ' Teachers ' , classroomID: @ classroom . id , url: @ state . get ( ' joinURL ' ) , [ ' Mixpanel ' ]
2016-04-19 16:44:48 -04:00
@ $ ( ' # join-url-input ' ) . val ( @ state . get ( ' joinURL ' ) ) . select ( )
2016-03-30 16:57:19 -04:00
@ tryCopy ( )
2016-05-05 12:54:21 -04:00
2016-04-19 16:44:48 -04:00
onClickUnarchive: ->
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Unarchive ' , category: ' Teachers ' , classroomID: @ classroom . id , [ ' Mixpanel ' ]
2016-04-19 16:44:48 -04:00
@ classroom . save { archived: false }
2016-07-17 03:53:17 -04:00
2016-03-30 16:57:19 -04:00
onClickEditClassroom: (e) ->
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Edit Class Started ' , category: ' Teachers ' , classroomID: @ classroom . id , [ ' Mixpanel ' ]
2016-03-30 16:57:19 -04:00
classroom = @ classroom
modal = new ClassroomSettingsModal ( { classroom: classroom } )
@ openModalView ( modal )
@ listenToOnce modal , ' hide ' , @ render
2016-05-05 12:54:21 -04:00
2016-05-11 17:39:26 -04:00
onClickEditStudentLink: (e) ->
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Students Edit ' , category: ' Teachers ' , classroomID: @ classroom . id , [ ' Mixpanel ' ]
2016-05-11 17:39:26 -04:00
user = @ students . get ( $ ( e . currentTarget ) . data ( ' student-id ' ) )
2016-05-20 17:52:04 -04:00
modal = new EditStudentModal ( { user , @ classroom } )
2016-05-11 17:39:26 -04:00
@ openModalView ( modal )
2016-03-30 19:20:37 -04:00
onClickRemoveStudentLink: (e) ->
user = @ students . get ( $ ( e . currentTarget ) . data ( ' student-id ' ) )
modal = new RemoveStudentModal ( {
classroom: @ classroom
user: user
courseInstances: @ courseInstances
} )
@ openModalView ( modal )
modal . once ' remove-student ' , @ onStudentRemoved , @
onStudentRemoved: (e) ->
@ students . remove ( e . user )
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Students Removed ' , category: ' Teachers ' , classroomID: @ classroom . id , userID: e . user . id , [ ' Mixpanel ' ]
2016-03-30 16:57:19 -04:00
onClickAddStudents: (e) =>
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Add Students ' , category: ' Teachers ' , classroomID: @ classroom . id , [ ' Mixpanel ' ]
2016-03-30 16:57:19 -04:00
modal = new InviteToClassroomModal ( { classroom: @ classroom } )
@ openModalView ( modal )
@ listenToOnce modal , ' hide ' , @ render
2016-05-05 12:54:21 -04:00
2016-04-07 17:55:42 -04:00
removeDeletedStudents: () ->
2016-04-19 16:44:48 -04:00
return unless @ classroom . loaded and @ students . loaded
2016-04-07 17:55:42 -04:00
_ . remove ( @ classroom . get ( ' members ' ) , (memberID) =>
not @ students . get ( memberID ) or @ students . get ( memberID ) ? . get ( ' deleted ' )
)
true
2016-05-05 12:54:21 -04:00
2016-05-09 18:16:54 -04:00
onClickSortButton: (e) ->
value = $ ( e . target ) . val ( )
if value is @ state . get ( ' sortValue ' )
2016-04-19 16:44:48 -04:00
@ state . set ( ' sortDirection ' , - @ state . get ( ' sortDirection ' ) )
2016-03-30 16:57:19 -04:00
else
2016-05-09 18:16:54 -04:00
@ state . set ( {
sortValue: value
sortDirection: 1
} )
2016-03-30 16:57:19 -04:00
@ students . sort ( )
2016-05-05 12:54:21 -04:00
2016-05-09 18:16:54 -04:00
onKeyPressStudentSearch: (e) ->
@ state . set ( ' searchTerm ' , $ ( e . target ) . val ( ) )
2016-05-05 12:54:21 -04:00
2016-05-26 18:01:16 -04:00
onChangeCourseSelect: (e) ->
@ trigger ' course-select:change ' , { selectedCourse: @ courses . get ( $ ( e . currentTarget ) . val ( ) ) }
2016-03-30 16:57:19 -04:00
getSelectedStudentIDs: ->
2016-07-07 16:06:15 -04:00
Object . keys ( _ . pick @ state . get ( ' checkboxStates ' ) , (checked) -> checked )
2016-05-05 12:54:21 -04:00
2016-03-30 16:57:19 -04:00
ensureInstance: (courseID) ->
2016-05-05 12:54:21 -04:00
2016-05-09 18:16:54 -04:00
onClickEnrollStudentButton: (e) ->
2016-03-30 16:57:19 -04:00
userID = $ ( e . currentTarget ) . data ( ' user-id ' )
user = @ students . get ( userID )
selectedUsers = new Users ( [ user ] )
2016-04-19 16:44:48 -04:00
@ enrollStudents ( selectedUsers )
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent $ ( e . currentTarget ) . data ( ' event-action ' ) , category: ' Teachers ' , classroomID: @ classroom . id , userID: userID , [ ' Mixpanel ' ]
2016-03-30 16:57:19 -04:00
onClickBulkEnroll: ->
2016-07-07 16:06:15 -04:00
userIDs = @ getSelectedStudentIDs ( )
2016-03-30 16:57:19 -04:00
selectedUsers = new Users ( @ students . get ( userID ) for userID in userIDs )
2016-04-19 16:44:48 -04:00
@ enrollStudents ( selectedUsers )
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Students Enroll Selected ' , category: ' Teachers ' , classroomID: @ classroom . id , [ ' Mixpanel ' ]
2016-04-19 16:44:48 -04:00
enrollStudents: (selectedUsers) ->
2016-03-30 16:57:19 -04:00
modal = new ActivateLicensesModal { @ classroom , selectedUsers , users: @ students }
@ openModalView ( modal )
2016-04-19 16:44:48 -04:00
modal . once ' redeem-users ' , (enrolledUsers) =>
enrolledUsers . each (newUser) =>
user = @ students . get ( newUser . id )
if user
user . set ( newUser . attributes )
null
2016-05-05 12:54:21 -04:00
onClickExportStudentProgress: ->
# TODO: Does not yield .csv download on Safari, and instead opens a new tab with the .csv contents
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Export CSV ' , category: ' Teachers ' , classroomID: @ classroom . id , [ ' Mixpanel ' ]
2016-06-09 17:44:43 -04:00
courseLabels = " "
courseOrder = [ ]
2016-07-16 03:31:53 -04:00
courses = ( @ courses . get ( c . _id ) for c in @ classroom . get ( ' courses ' ) )
2016-07-17 04:12:58 -04:00
courseLabelsArray = helper . courseLabelsArray courses
2016-07-16 03:31:53 -04:00
for course , index in courses
courseLabels += " #{ courseLabelsArray [ index ] } Playtime, "
courseOrder . push ( course . id )
2016-06-09 17:44:43 -04:00
csvContent = " data:text/csv;charset=utf-8,Username,Email,Total Playtime, #{ courseLabels } Concepts \n "
levelCourseMap = { }
2016-08-16 18:19:03 -04:00
language = @ classroom . get ( ' aceConfig ' ) ? . language
2016-06-13 17:30:56 -04:00
for trimCourse in @ classroom . get ( ' courses ' )
for trimLevel in trimCourse . levels
2016-08-16 18:19:03 -04:00
continue if language and trimLevel . primerLanguage is language
2016-06-13 17:30:56 -04:00
levelCourseMap [ trimLevel . original ] = @ courses . get ( trimCourse . _id )
2016-05-05 12:54:21 -04:00
for student in @ students . models
concepts = [ ]
2016-06-13 17:30:56 -04:00
for trimCourse in @ classroom . get ( ' courses ' )
course = @ courses . get ( trimCourse . _id )
2016-05-05 12:54:21 -04:00
instance = @ courseInstances . findWhere ( { courseID: course . id , classroomID: @ classroom . id } )
2016-04-19 16:44:48 -04:00
if instance and instance . hasMember ( student )
2016-06-13 17:30:56 -04:00
for trimLevel in trimCourse . levels
level = @ levels . findWhere ( { original: trimLevel . original } )
2016-04-19 16:44:48 -04:00
progress = @ state . get ( ' progressData ' ) . get ( { classroom: @ classroom , course: course , level: level , user: student } )
2016-05-05 12:54:21 -04:00
concepts . push ( level . get ( ' concepts ' ) ? [ ] ) if progress ? . completed
concepts = _ . union ( _ . flatten ( concepts ) )
conceptsString = _ . map ( concepts , (c) -> $ . i18n . t ( " concepts. " + c ) ) . join ( ' , ' )
2016-06-09 17:44:43 -04:00
coursePlaytimeMap = { }
2016-05-05 12:54:21 -04:00
playtime = 0
for session in @ classroom . sessions . models when session . get ( ' creator ' ) is student . id
playtime += session . get ( ' playtime ' ) or 0
2016-06-09 17:44:43 -04:00
if courseID = levelCourseMap [ session . get ( ' level ' ) ? . original ] ? . id
coursePlaytimeMap [ courseID ] ? = 0
coursePlaytimeMap [ courseID ] += session . get ( ' playtime ' ) or 0
playtimeString = if playtime is 0 then " 0 " else moment . duration ( playtime , ' seconds ' ) . humanize ( )
2016-07-29 17:36:37 -04:00
for course in courses
2016-06-09 17:44:43 -04:00
coursePlaytimeMap [ course . id ] ? = 0
coursePlaytimes = [ ]
for courseID , playtime of coursePlaytimeMap
coursePlaytimes . push
courseID: courseID
playtime: playtime
coursePlaytimes . sort (a, b) ->
return - 1 if courseOrder . indexOf ( a . courseID ) < courseOrder . indexOf ( b . courseID )
return 0 if courseOrder . indexOf ( a . courseID ) is courseOrder . indexOf ( b . courseID )
return 1
coursePlaytimesString = " "
for coursePlaytime , index in coursePlaytimes
if coursePlaytime . playtime is 0
coursePlaytimesString += " 0, "
else
coursePlaytimesString += " #{ moment . duration ( coursePlaytime . playtime , ' seconds ' ) . humanize ( ) } , "
2016-07-13 19:50:03 -04:00
csvContent += " #{ student . get ( ' name ' ) } , #{ student . get ( ' email ' ) or ' ' } , #{ playtimeString } , #{ coursePlaytimesString } \" #{ conceptsString } \" \n "
2016-05-05 12:54:21 -04:00
csvContent = csvContent . substring ( 0 , csvContent . length - 1 )
encodedUri = encodeURI ( csvContent )
window . open ( encodedUri )
2016-05-09 18:16:54 -04:00
onClickAssignStudentButton: (e) ->
2016-04-19 16:44:48 -04:00
userID = $ ( e . currentTarget ) . data ( ' user-id ' )
user = @ students . get ( userID )
members = [ userID ]
courseID = $ ( e . currentTarget ) . data ( ' course-id ' )
@ assignCourse courseID , members
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Students Assign Selected ' , category: ' Teachers ' , classroomID: @ classroom . id , courseID: courseID , userID: userID , [ ' Mixpanel ' ]
2016-03-30 16:57:19 -04:00
onClickBulkAssign: ->
2016-04-07 17:55:42 -04:00
courseID = @ $ ( ' .bulk-course-select ' ) . val ( )
selectedIDs = @ getSelectedStudentIDs ( )
2016-07-07 16:06:15 -04:00
members = selectedIDs . filter (userID) =>
2016-03-30 16:57:19 -04:00
user = @ students . get ( userID )
user . isEnrolled ( )
2016-04-19 16:44:48 -04:00
assigningToUnenrolled = _ . any selectedIDs , (userID) =>
2016-04-07 17:55:42 -04:00
not @ students . get ( userID ) . isEnrolled ( )
2016-04-19 16:44:48 -04:00
assigningToNobody = selectedIDs . length is 0
@ state . set errors: { assigningToNobody , assigningToUnenrolled }
2016-07-16 02:26:43 -04:00
return if assigningToNobody
2016-04-19 16:44:48 -04:00
@ assignCourse courseID , members
2016-06-08 09:24:59 -04:00
window . tracker ? . trackEvent ' Teachers Class Students Assign Selected ' , category: ' Teachers ' , classroomID: @ classroom . id , courseID: courseID , [ ' Mixpanel ' ]
2016-04-19 16:44:48 -04:00
# TODO: Move this to the model. Use promises/callbacks?
assignCourse: (courseID, members) ->
courseInstance = @ courseInstances . findWhere ( { courseID , classroomID: @ classroom . id } )
2016-03-30 16:57:19 -04:00
if courseInstance
2016-04-19 16:44:48 -04:00
courseInstance . addMembers members
2016-03-30 16:57:19 -04:00
else
courseInstance = new CourseInstance {
courseID ,
classroomID: @ classroom . id
ownerID: @ classroom . get ( ' ownerID ' )
aceConfig: { }
}
@ courseInstances . add ( courseInstance )
courseInstance . save { } , {
2016-04-19 16:44:48 -04:00
success: ->
courseInstance . addMembers members
2016-03-30 16:57:19 -04:00
}
null
2016-05-09 18:16:54 -04:00
2016-03-30 16:57:19 -04:00
onClickSelectAll: (e) ->
e . preventDefault ( )
2016-07-07 16:06:15 -04:00
checkboxStates = _ . clone @ state . get ( ' checkboxStates ' )
if _ . all ( checkboxStates )
for studentID of checkboxStates
checkboxStates [ studentID ] = false
2016-03-30 16:57:19 -04:00
else
2016-07-07 16:06:15 -04:00
for studentID of checkboxStates
checkboxStates [ studentID ] = true
@ state . set { checkboxStates }
2016-05-05 12:54:21 -04:00
2016-03-30 16:57:19 -04:00
onClickStudentCheckbox: (e) ->
e . preventDefault ( )
checkbox = $ ( e . currentTarget ) . find ( ' input ' )
2016-07-07 16:06:15 -04:00
studentID = checkbox . data ( ' student-id ' )
checkboxStates = _ . clone @ state . get ( ' checkboxStates ' )
checkboxStates [ studentID ] = not checkboxStates [ studentID ]
@ state . set { checkboxStates }
2016-05-05 12:54:21 -04:00
2016-04-19 16:44:48 -04:00
calculateClassStats: ->
return { } unless @ classroom . sessions ? . loaded and @ students . loaded
2016-03-30 16:57:19 -04:00
stats = { }
playtime = 0
total = 0
for session in @ classroom . sessions . models
pt = session . get ( ' playtime ' ) or 0
playtime += pt
total += 1
stats.averagePlaytime = if playtime and total then moment . duration ( playtime / total , " seconds " ) . humanize ( ) else 0
stats.totalPlaytime = if playtime then moment . duration ( playtime , " seconds " ) . humanize ( ) else 0
# TODO: Humanize differently ('1 hour' instead of 'an hour')
2016-08-16 18:19:03 -04:00
levelIncludeMap = { }
language = @ classroom . get ( ' aceConfig ' ) ? . language
for level in @ levels . models
levelIncludeMap [ level . get ( ' original ' ) ] = not level . get ( ' practice ' ) and ( not language ? or level . get ( ' primerLanguage ' ) isnt language )
completeSessions = @ classroom . sessions . filter (s) -> s . get ( ' state ' ) ? . complete and levelIncludeMap [ s . get ( ' level ' ) ? . original ]
2016-03-30 16:57:19 -04:00
stats.averageLevelsComplete = if @ students . size ( ) then ( _ . size ( completeSessions ) / @ students . size ( ) ) . toFixed ( 1 ) else ' N/A ' # '
stats.totalLevelsComplete = _ . size ( completeSessions )
2016-05-09 18:16:54 -04:00
enrolledUsers = @ students . filter (user) -> user . isEnrolled ( )
2016-03-30 16:57:19 -04:00
stats.enrolledUsers = _ . size ( enrolledUsers )
2016-07-17 03:53:17 -04:00
2016-03-30 16:57:19 -04:00
return stats
2016-05-09 18:16:54 -04:00
studentStatusString: (student) ->
status = student . prepaidStatus ( )
expires = student . get ( ' coursePrepaid ' ) ? . endDate
string = switch status
when ' not-enrolled ' then $ . i18n . t ( ' teacher.status_not_enrolled ' )
when ' enrolled ' then ( if expires then $ . i18n . t ( ' teacher.status_enrolled ' ) else ' - ' )
when ' expired ' then $ . i18n . t ( ' teacher.status_expired ' )
return string . replace ( ' {{date}} ' , moment ( expires ) . utc ( ) . format ( ' l ' ) )