Skip to content

Commit 6d2aa76

Browse files
Add Intro Project Results management
1 parent 99d9e32 commit 6d2aa76

File tree

9 files changed

+268
-18
lines changed

9 files changed

+268
-18
lines changed

conditional/blueprints/member_management.py

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import structlog
99

10-
from flask import Blueprint, request, jsonify
10+
from flask import Blueprint, request, jsonify, abort
1111

1212
from conditional.models.models import FreshmanAccount
1313
from conditional.models.models import FreshmanEvalData
@@ -24,6 +24,7 @@
2424

2525
from conditional.blueprints.cache_management import clear_active_members_cache
2626
from conditional.blueprints.cache_management import clear_onfloor_members_cache
27+
from conditional.blueprints.intro_evals import display_intro_evals
2728

2829
from conditional.util.ldap import ldap_is_eval_director
2930
from conditional.util.ldap import ldap_is_financial_director
@@ -241,6 +242,7 @@ def member_management_edituser(uid):
241242
db.session.commit()
242243
return jsonify({"success": True}), 200
243244

245+
244246
def edit_uid(uid, user_name, post_data):
245247
active_member = post_data['activeMember']
246248

@@ -290,8 +292,8 @@ def edit_uid(uid, user_name, post_data):
290292

291293
def edit_fid(uid, post_data):
292294
logger.info('backend', action="edit freshman account %s room: %s onfloor: %s eval_date: %s sig_missed %s" %
293-
(uid, post_data['roomNumber'], post_data['onfloorStatus'],
294-
post_data['evalDate'], post_data['sigMissed']))
295+
(uid, post_data['roomNumber'], post_data['onfloorStatus'],
296+
post_data['evalDate'], post_data['sigMissed']))
295297

296298
name = post_data['name']
297299

@@ -473,13 +475,13 @@ def member_management_upgrade_user():
473475
for fhm in FreshmanHouseMeetingAttendance.query.filter(FreshmanHouseMeetingAttendance.fid == fid):
474476
# Don't duplicate HM attendance records
475477
mhm = MemberHouseMeetingAttendance.query.filter(
476-
MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id).first()
478+
MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id).first()
477479
if mhm is None:
478480
db.session.add(MemberHouseMeetingAttendance(
479481
uid, fhm.meeting_id, fhm.excuse, fhm.attendance_status))
480482
else:
481483
logger.info('backend', action="duplicate house meeting attendance! fid: %s, uid: %s, id: %s" %
482-
(fid, uid, fhm.meeting_id))
484+
(fid, uid, fhm.meeting_id))
483485
db.session.delete(fhm)
484486

485487
if acct.onfloor_status:
@@ -496,3 +498,58 @@ def member_management_upgrade_user():
496498
clear_onfloor_members_cache()
497499

498500
return jsonify({"success": True}), 200
501+
502+
503+
@member_management_bp.route('/manage/intro_project', methods=['GET'])
504+
def introductory_project():
505+
log = logger.new(user_name=request.headers.get("x-webauth-user"),
506+
request_id=str(uuid.uuid4()))
507+
log.info('api', action='show introductory project management')
508+
509+
user_name = request.headers.get('x-webauth-user')
510+
511+
if not ldap_is_eval_director(user_name):
512+
return "must be eval director", 403
513+
514+
return render_template(request,
515+
'introductory_project.html',
516+
username=user_name,
517+
intro_members=display_intro_evals(internal=True))
518+
519+
520+
@member_management_bp.route('/manage/intro_project', methods=['POST'])
521+
def introductory_project_submit():
522+
log = logger.new(user_name=request.headers.get("x-webauth-user"),
523+
request_id=str(uuid.uuid4()))
524+
log.info('api', action='submit introductory project results')
525+
526+
user_name = request.headers.get('x-webauth-user')
527+
528+
if not ldap_is_eval_director(user_name):
529+
return "must be eval director", 403
530+
531+
post_data = request.get_json()
532+
533+
if not isinstance(post_data, list):
534+
abort(400)
535+
536+
for intro_member in post_data:
537+
if not isinstance(intro_member, dict):
538+
abort(400)
539+
540+
if 'uid' not in intro_member or 'status' not in intro_member:
541+
abort(400)
542+
543+
if intro_member['status'] not in ['Passed', 'Pending', 'Failed']:
544+
abort(400)
545+
546+
log.info('debug', action='setting status "' + intro_member['status'] + '" for ' + intro_member['uid'])
547+
548+
FreshmanEvalData.query.filter(FreshmanEvalData.uid == intro_member['uid']).update({
549+
'freshman_project': intro_member['status']
550+
})
551+
552+
db.session.flush()
553+
db.session.commit()
554+
555+
return jsonify({"success": True}), 200

conditional/templates/intro_eval_slideshow.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ <h3>{{m['technical_seminars']|length}}</h3>
3939
</div>
4040
</div>
4141
</div>
42-
{% set freshman_project_passed = m['freshman_project'] == 'Passed' %}
43-
<h4><span class="icon glyphicon glyphicon-{% if freshmen_project_passed %}ok passed{%else%}remove{% endif %}" aria-hidden="true"></span> Freshman Project</h4>
42+
<h4><span class="icon glyphicon glyphicon-{% if m['freshman_project'] == 'Passed' %}ok passed{%else%}remove{% endif %}" aria-hidden="true"></span> Freshman Project</h4>
4443

4544
<div class="actions" data-uid="{{m['uid']}}" data-cn="{{m['name']}}">
4645
<button class="pass" type="button">Pass</button>

conditional/templates/intro_evals.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,20 @@ <h6 class="eval-uid">{{ m['uid'] }}</h6>
7575

7676
</div>
7777
<div class="text-center">
78-
{% if m['freshman_project'] == "Pending" %}
79-
<div class="eval-info-label">
80-
<span class="glyphicon glyphicon-hourglass yellow eval-info-status"></span>Freshmen Project
81-
<span class="eval-info-number">Pending</span>
82-
</div>
83-
{% elif m['freshman_project'] == "Passed" %}
78+
{% if m['freshman_project'] == "Passed" %}
8479
<div class="eval-info-label">
8580
<span class="glyphicon glyphicon-ok-sign green eval-info-status"></span>Freshmen Project
8681
<span class="eval-info-number">Passed</span>
8782
</div>
83+
{% elif m['freshman_project'] == "Failed" %}
84+
<div class="eval-info-label">
85+
<span class="glyphicon glyphicon-remove-sign red eval-info-status"></span>Freshmen Project
86+
<span class="eval-info-number">Failed</span>
87+
</div>
8888
{% else %}
8989
<div class="eval-info-label">
90-
<span class="glyphicon glyphicon-ok-sign green eval-info-status"></span>Freshmen Project
91-
<span class="eval-info-number">Passed</span>
90+
<span class="glyphicon glyphicon-hourglass yellow eval-info-status"></span>Freshmen Project
91+
<span class="eval-info-number">Pending</span>
9292
</div>
9393
{% endif %}
9494

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{% extends "nav.html" %}
2+
{% block title %}
3+
Introductory Project Results
4+
{% endblock %}
5+
{% block body %}
6+
<div class="container main">
7+
<h3>Introductory Project Results</h3>
8+
<div style="padding-top:20px"></div>
9+
<form data-module="introductoryProject">
10+
<!-- Intro Members -->
11+
<div class="panel panel-default">
12+
<div class="panel-heading">
13+
<h3 class="panel-title">Introductory Members</h3>
14+
</div>
15+
<div class="panel-body table-fill">
16+
<div class="hm-search">
17+
<input class="form-control" type="text" placeholder="Quick Select" data-module="ipSearch" data-target="table.intro-project">
18+
</div>
19+
<div class="table-responsive intro-project-container">
20+
<table class="table table-striped no-bottom-margin intro-project" data-module="table" data-sort-column="0" data-sort-order="asc" data-paginated="false">
21+
<thead>
22+
<tr>
23+
<th>Name</th>
24+
<th>Result</th>
25+
</tr>
26+
</thead>
27+
<tbody>
28+
{% for f in intro_members %}
29+
<tr data-uid="{{f['uid']}}">
30+
<td>
31+
<img class="table-img mobile-hide" src="https://profiles.csh.rit.edu/image/{{f['uid']}}"> {{f['name']}}
32+
</td>
33+
<td>
34+
<div class="btn-group">
35+
<a href="#" class="btn {% if f['freshman_project'] == 'Passed' %}btn-success{% elif f['freshman_project'] == 'Failed' %}btn-danger{% else %}btn-warning{% endif %} dropdown-toggle btn-mp" data-toggle="dropdown" data-selected="{{f['freshman_project']}}" aria-expanded="false">
36+
{{f['freshman_project']}}
37+
<span class="caret"></span>
38+
</a>
39+
<ul class="dropdown-menu">
40+
<li>
41+
<a href="#" data-option="Passed"><span class="glyphicon glyphicon-ok-sign green"></span> Passed</a>
42+
</li>
43+
<li>
44+
<a href="#" data-option="Pending"><span class="glyphicon glyphicon-hourglass yellow"></span> Pending</a>
45+
</li>
46+
<li>
47+
<a href="#" data-option="Failed"><span class="glyphicon glyphicon-remove-sign red"></span> Failed</a>
48+
</li>
49+
</ul>
50+
</div>
51+
</td>
52+
</tr>
53+
{% endfor %}
54+
</tbody>
55+
</table>
56+
</div>
57+
</div>
58+
</div>
59+
<input type="submit" role="button" class="btn btn-raised btn-primary" style="width:100%;" value="Save Results"/>
60+
</form>
61+
</div>
62+
{% endblock %}

conditional/templates/nav.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<li><a href="/manage"><span class="glyphicon glyphicon-user"></span> Member Management</a></li>
5252
{% if is_eval_director %}
5353

54+
<li><a href="/manage/intro_project"><span class="glyphicon glyphicon-tower"></span> Introductory Project Results</a></li>
5455
<li><a href="/slideshow/intro"><span class="glyphicon glyphicon-eye-open"></span> Introductory Evaluations Presentation</a></li>
5556
<li><a href="/slideshow/spring"><span class="glyphicon glyphicon-eye-open"></span> Membership Evaluations Presentation</a></li>
5657

frontend/javascript/modules/hmSearch.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ export default class HouseMeetingSearch {
4949
// Yes, prevent form submission
5050
event.preventDefault();
5151

52-
// Check the first visible table row's checkbox
53-
this.api.table().body().firstElementChild
54-
.querySelector("input[type=checkbox]").checked = true;
52+
this._handleKeyAction();
5553

5654
// Reset the table
5755
this.api.search('').draw();
@@ -65,6 +63,12 @@ export default class HouseMeetingSearch {
6563
}
6664
}
6765

66+
_handleKeyAction() {
67+
// Check the first visible table row's checkbox
68+
this.api.table().body().firstElementChild
69+
.querySelector("input[type=checkbox]").checked = true;
70+
}
71+
6872
/*
6973
* Custom filtering function that will remove rows that are already selected
7074
*/
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import _ from "lodash";
2+
import FetchUtil from "../utils/fetchUtil";
3+
4+
export default class IntroductoryProjectForm {
5+
constructor(form) {
6+
this.form = form;
7+
this.endpoint = '/manage/intro_project';
8+
9+
this.render();
10+
}
11+
12+
render() {
13+
// Prevent the form from submitting if the user hits the enter key
14+
['keyup', 'keypress'].forEach(keyevent =>
15+
this.form.addEventListener(keyevent, event => {
16+
let keyCode = event.keyCode || event.which;
17+
if (keyCode === 13) {
18+
event.preventDefault();
19+
return false;
20+
}
21+
}, true));
22+
23+
this.form.querySelectorAll('tbody > tr .btn-group').forEach(control => {
24+
control.querySelectorAll('[data-option]').forEach(option => {
25+
option.addEventListener('click', e => {
26+
e.preventDefault();
27+
28+
let toggle = control.querySelector('.dropdown-toggle');
29+
30+
["btn-success", "btn-danger", "btn-warning"]
31+
.forEach(classToRemove =>
32+
toggle.classList.remove(classToRemove));
33+
34+
const caret = document.createElement('span');
35+
caret.classList.add('caret');
36+
toggle.text = option.dataset.option + " ";
37+
toggle.appendChild(caret);
38+
toggle.dataset.selected = option.dataset.option;
39+
40+
if (option.dataset.option === "Passed") {
41+
toggle.classList.add("btn-success");
42+
} else if (option.dataset.option === "Failed") {
43+
toggle.classList.add("btn-danger");
44+
} else {
45+
toggle.classList.add("btn-warning");
46+
}
47+
});
48+
});
49+
});
50+
51+
// Form submit handler
52+
this.form.querySelectorAll("input[type=submit]").forEach(submitBtn => {
53+
submitBtn.addEventListener("click", e => {
54+
e.preventDefault();
55+
56+
let payload = [];
57+
58+
this.form.querySelectorAll("tbody > tr").forEach(freshman => {
59+
const uid = freshman.dataset.uid;
60+
const status = freshman.querySelector('.dropdown-toggle')
61+
.dataset.selected;
62+
63+
// Quick sanity check
64+
if (!_.isNil(uid) && !_.isNil(status) &&
65+
(status === "Passed" || status === "Pending" ||
66+
status === "Failed")) {
67+
payload.push({
68+
uid: uid,
69+
status: status
70+
});
71+
}
72+
});
73+
74+
FetchUtil.postWithWarning(this.endpoint, payload, {
75+
warningText: "Are you sure you want to update the introductory " +
76+
"project results?",
77+
successText: "Introductory project results have been submitted."
78+
});
79+
});
80+
});
81+
}
82+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import HouseMeetingSearch from "./hmSearch";
2+
3+
export default class IntroductoryProjectSearch extends HouseMeetingSearch {
4+
constructor(input) {
5+
super(input);
6+
HouseMeetingSearch._alreadySelectedFilter = (settings, data, dataIndex) => {
7+
// Only apply the filter if we're currently searching
8+
if (typeof settings.oPreviousSearch.sSearch !== "undefined" &&
9+
settings.oPreviousSearch.sSearch !== "") {
10+
return !(settings.aoData[dataIndex].anCells[1]
11+
.querySelector('.dropdown-toggle').dataset.selected === "Passed");
12+
}
13+
14+
return true;
15+
};
16+
}
17+
18+
_handleKeyAction() {
19+
// Set the status of the first visible table row's selector to Passed
20+
let toggle = this.api.table().body().firstElementChild
21+
.querySelector(".dropdown-toggle");
22+
23+
["btn-success", "btn-danger", "btn-warning"]
24+
.forEach(classToRemove =>
25+
toggle.classList.remove(classToRemove));
26+
27+
const caret = document.createElement('span');
28+
caret.classList.add('caret');
29+
toggle.text = "Passed ";
30+
toggle.appendChild(caret);
31+
toggle.classList.add("btn-success");
32+
toggle.dataset.selected = "Passed";
33+
}
34+
}

frontend/stylesheets/pages/_management.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,14 @@
140140
text-transform: uppercase;
141141
font-size: 10px;
142142
}
143+
144+
.intro-project-container {
145+
overflow-x: visible !important;
146+
overflow-y: visible !important;
147+
}
148+
149+
.intro-project {
150+
.btn-group {
151+
margin-top: -22px;
152+
}
153+
}

0 commit comments

Comments
 (0)