Skip to content

Commit

Permalink
Merge pull request #459 from BCStudentSoftwareDevTeam/441simple_view
Browse files Browse the repository at this point in the history
Create simple and advanced views for supervisor portal
  • Loading branch information
BrianRamsay authored Nov 19, 2024
2 parents e48bf15 + 16ef197 commit d2acb2e
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 43 deletions.
76 changes: 49 additions & 27 deletions app/controllers/main_routes/main_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def supervisorPortal():
allSupervisors = Supervisor.select()
supervisorFirstName = fn.COALESCE(Supervisor.preferred_name, Supervisor.legal_name)
studentFirstName = fn.COALESCE(Student.preferred_name, Student.legal_name)

department = None
if currentUser.isLaborAdmin or currentUser.isFinancialAidAdmin or currentUser.isSaasAdmin:
departments = Department.select().order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())
departments = [department for department in departments]
Expand All @@ -63,6 +63,8 @@ def supervisorPortal():
departments = [department for department in departments]
deptNames = [department.DEPT_NAME for department in departments]

supervisorPrimaryDepartment = Department.select().join(SupervisorDepartment) # count up all forms for a supervisor in department and get the max

supervisors = (Supervisor.select(Supervisor, supervisorFirstName)
.join_from(Supervisor, LaborStatusForm)
.join_from(LaborStatusForm, Department)
Expand All @@ -85,7 +87,7 @@ def supervisorPortal():
allSupervisors = allSupervisors,
students = students,
departments = departments,
department = None,
department = department,
currentUser = currentUser
)

Expand All @@ -101,9 +103,9 @@ def getDatatableData(request):
sleJoin = False

currentUser = require_login()
draw = int(request.form.get('draw', -1))
rowNumber = int(request.form.get('start', -1))
rowsPerPage = int(request.form.get('length', -1))
draw = int(request.form.get('draw', 0))
rowNumber = int(request.form.get('start', 0))
rowsPerPage = int(request.form.get('length', 25))
queryFilterData = request.form.get('data')
queryFilterDict = json.loads(queryFilterData)
sortBy = queryFilterDict.get('sortBy', "term")
Expand Down Expand Up @@ -167,36 +169,53 @@ def getDatatableData(request):

# this maps all of the values we expect to receive from the sorting dropdowns in the frontend
# to actual peewee objects we can sort by later
# the casing is weird because the columns that don't have any fields are are not capitalized
sortValueColumnMap = {
"term": Term.termCode,
"department": Department.DEPT_NAME,
"supervisorFirstName": supervisorFirstNameCase,
"supervisorLastName": Supervisor.LAST_NAME,
"studentFirstName": studentFirstNameCase,
"studentLastName": Student.LAST_NAME,
"positionCode": LaborStatusForm.POSN_CODE,
"positionWLS": LaborStatusForm.WLS,
"positionTitle": LaborStatusForm.POSN_TITLE,
"positionType": LaborStatusForm.jobType,
"length": LaborStatusForm.startDate,
"createdBy": User.username,
"formStatus": FormHistory.status,
"formType": FormHistory.historyType,
}

if order == "DESC":
filteredSearchResults = formSearchResults.order_by(sortValueColumnMap[sortBy].desc()).limit(rowsPerPage).offset(rowNumber)
filteredSearchResults = formSearchResults.order_by(fn.TRIM(sortValueColumnMap[sortBy]).desc()).limit(rowsPerPage).offset(rowNumber)
else:
filteredSearchResults = formSearchResults.order_by(sortValueColumnMap[sortBy].asc()).limit(rowsPerPage).offset(rowNumber)
formattedData = getFormattedData(filteredSearchResults)
filteredSearchResults = formSearchResults.order_by(fn.TRIM(sortValueColumnMap[sortBy]).asc()).limit(rowsPerPage).offset(rowNumber)
formattedData = getFormattedData(filteredSearchResults, queryFilterDict['view'])
formsDict = {"draw": draw, "recordsTotal": recordsTotal, "recordsFiltered": recordsTotal, "data": formattedData}

return jsonify(formsDict)

def getFormattedData(filteredSearchResults):
def getFormattedData(filteredSearchResults, view ='simple'):
'''
Putting the data in the correct format to be used by the JS file.
Because this implementation is using server-side processing of datatables,
the HTML for the datatables are also formatted here.
'''
if view == "simple":
formattedData = []
for form in filteredSearchResults:
# The order in which you append the items to 'record' matters and it should match the order of columns on the table!
formattedData.append([f"""
<a>
<span onclick=loadLaborHistoryModal({form.formID.laborStatusFormID}) value=0>
<span class="h4">{form.formID.studentSupervisee.FIRST_NAME} {form.formID.studentSupervisee.LAST_NAME} ({form.formID.studentSupervisee.ID})</span>
</span>
</a>
<span class="pushRight">{form.status}</span>
<br>
<span class="pushLeft h6"> {form.formID.termCode.termName} - {form.formID.POSN_TITLE} ({form.formID.jobType}) - {form.formID.department.DEPT_NAME}</span>
"""])
return formattedData

supervisorHTML = '<span href="#" aria-label="{}">{} </span><a href="mailto:{}"><span class="glyphicon glyphicon-envelope mailtoIcon"></span></span>'
studentHTML = '<div><a><span onclick=loadLaborHistoryModal({}) aria-label="{}">{} </span></a><br>{} <a href="mailto:{}"><span class="glyphicon glyphicon-envelope mailtoIcon"></span></span></a></div>'
Expand All @@ -209,20 +228,30 @@ def getFormattedData(filteredSearchResults):
record = []
# Term
record.append(form.formID.termCode.termName)
# Department
record.append(departmentHTML.format(
form.formID.department.ORG,
form.formID.department.ACCOUNT,
form.formID.department.DEPT_NAME))
# Student
record.append(studentHTML.format(
form.formID.laborStatusFormID,
form.formID.studentSupervisee.ID,
f'{form.formID.studentSupervisee.preferred_name if form.formID.studentSupervisee.preferred_name else form.formID.studentSupervisee.legal_name} {form.formID.studentSupervisee.LAST_NAME}',
form.formID.studentSupervisee.ID,
form.formID.studentSupervisee.STU_EMAIL))
# Supervisor
supervisorField = supervisorHTML.format(
form.formID.supervisor.ID,
f'{form.formID.supervisor.preferred_name if form.formID.supervisor.preferred_name else form.formID.supervisor.legal_name } {form.formID.supervisor.LAST_NAME}',
form.formID.supervisor.EMAIL)
record.append(supervisorField)

# Department
record.append(departmentHTML.format(
form.formID.department.ORG,
form.formID.department.ACCOUNT,
form.formID.department.DEPT_NAME))

# Position
positionField = positionHTML.format(
form.formID.POSN_TITLE,
f'{form.formID.POSN_CODE} ({form.formID.WLS})')
form.formID.jobType,
f'{form.formID.jobType} ({form.formID.WLS})')
# Hours
hoursField = form.formID.weeklyHours if form.formID.weeklyHours else form.formID.contractHours
# Adjustment Form Specific Data
Expand All @@ -245,16 +274,10 @@ def getFormattedData(filteredSearchResults):
newHours = form.adjustedForm.newValue
hoursField = f'<s aria-label="true">{hoursField}</s><br>{newHours}'

record.append(supervisorField)
# Student
record.append(studentHTML.format(
form.formID.laborStatusFormID,
form.formID.studentSupervisee.ID,
f'{form.formID.studentSupervisee.preferred_name if form.formID.studentSupervisee.preferred_name else form.formID.studentSupervisee.legal_name} {form.formID.studentSupervisee.LAST_NAME}',
form.formID.studentSupervisee.ID,
form.formID.studentSupervisee.STU_EMAIL))



record.append(f'{form.formID.jobType}<br>{positionField}')
record.append(f'{form.formID.POSN_TITLE}<br>{positionField}')
record.append(hoursField)
# Contract Dates
record.append("<br>".join([form.formID.startDate.strftime('%m/%d/%y'),
Expand All @@ -280,7 +303,6 @@ def getFormattedData(filteredSearchResults):
# TODO: Skipping adding to the table. Requires database work to get SLE out from form (formHistory, to be precise)

formattedData.append(record)

return formattedData

@main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST'])
Expand Down
118 changes: 109 additions & 9 deletions app/static/js/supervisorPortal.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
$(document).ready(function () {
if ((document.cookie).includes("lsfSearchResults=")) {
cookieStr = Cookies.get('lsfSearchResults')
createDataTable(cookieStr)
setFormSearchValues(JSON.parse(cookieStr))
cookieJSON = JSON.parse(cookieStr)
// using the cookies, make sure the view is properly set as well
if (cookieJSON.view == 'advanced') {
createDataTable(cookieStr)
switchViewButton('simple')
} else {
fetchSimpleView(cookieStr)
switchViewButton('advanced')
}
setFormSearchValues(cookieJSON)

} else {
$('#formSearchTable').hide();
Expand All @@ -15,6 +23,18 @@ $(document).ready(function () {
runFormSearchQuery();
$('#sortOptions').show();
});

$('#switchViewButton').on('click', function () {
// toggle the view and button value
buttonVal = $("#switchViewButton").val()
switchViewButton(buttonVal)

// we can just rerun the form search query as it pulls down the value
// of the button to determine what button to render
runFormSearchQuery();
$('#sortOptions').show();
});

$('#addUserToDept').on('click', function () {
$("#addSupervisorToDeptModal").modal("show");
$('#addUser').prop('disabled', true)
Expand Down Expand Up @@ -70,8 +90,9 @@ $(document).ready(function () {
});
$('#columnPicker').on('change', function () {
let column = $('#columnPicker :selected').text()
let fields = columnFieldMap[column]

buttonVal = $("#switchViewButton").val()
let fields = buttonVal == "advanced" ? advancedColumnFieldMap[column] : simpleColumnFieldMap[column];

// clear the options from the current field picker and replace
// them with the ones from the columnFieldMap
$('#fieldPicker').empty();
Expand All @@ -97,17 +118,25 @@ $(document).ready(function () {

// this is a mapping which maps the column option to its field options.
// many do not have multiple fields so the field is just the column itself (e.g. term)
const columnFieldMap = {
const advancedColumnFieldMap = {
'Term': [['Term', 'term']],
'Department': [['Department', 'department']],
'Supervisor': [['First name', 'supervisorFirstName'], ['Last Name', 'supervisorLastName']],
'Student': [['First name', 'studentFirstName'], ['Last Name', 'studentLastName']],
'Position (WLS)': [['WLS', 'positionWLS'], ['Position Code', 'positionCode']],
'Position (WLS)': [['WLS', 'positionWLS'], ['Position Type', 'positionType'], ['Position Title', 'positionTitle']],
'Length': [['Length', 'length']],
'Created By': [['Created By', 'createdBy']],
'Form Type (Status)': [['Form Type', 'formType'], ['Status', 'formStatus']]
};

const simpleColumnFieldMap = {
'Term': [['Term', 'term']],
'Department': [['Department', 'department']],
'Student': [['First name', 'studentFirstName'], ['Last Name', 'studentLastName']],
'Position': [['Position Type', 'positionType'], ['Position Title', 'positionTitle']],
'Form Status': [['Status', 'formStatus']]
};


function disableButtonHandler() {
if ($('#departmentModalSelect :selected').val() == "" || $('#supervisorModalSelect :selected').val() == "") {
Expand All @@ -119,11 +148,13 @@ function disableButtonHandler() {
}

function runFormSearchQuery(button) {
let view = $('#switchViewButton').val()
let termCode, departmentID, supervisorID, studentID;
let formStatusList = [];
let formTypeList = [];
var isDisabled = $('#fieldPicker').prop('disabled');
let sortBy = $('#fieldPicker').val()


// if the fieldPicker is disabled that means we should take the value
// from the columnPicker instead
Expand All @@ -138,7 +169,7 @@ function runFormSearchQuery(button) {
departmentID = ""
supervisorID = "currentUser"
studentID = ""
formStatusList = []
formStatusList = ["Approved", "Approved Reluctantly"]
break;

case "pendingForms":
Expand Down Expand Up @@ -168,6 +199,7 @@ function runFormSearchQuery(button) {
}

queryDict = {
'view': view,
'termCode': termCode,
'departmentID': departmentID,
'supervisorID': supervisorID,
Expand All @@ -182,14 +214,82 @@ function runFormSearchQuery(button) {

var inAnHour = new Date(new Date().getTime() + 60 * 60 * 1000);
Cookies.set('lsfSearchResults', data, { expires: inAnHour })
if (view === 'advanced') {
createDataTable(data)
} else {
fetchSimpleView(data)
}
}

function resetColumns(columnFieldMap) {
// clear the current columnPicker options and populate it with new ones
// from either the simple or advanced columnFieldMap
$('#columnPicker').empty();
let columns = Object.keys(columnFieldMap)
columns.forEach((column) => {
var option = $('<option>', {
value: columnFieldMap[column][0][1],
text: column
});
$('#columnPicker').append(option)
})
}

function switchViewButton(view) {
if (view == 'advanced') {
$('#switchViewButton').val('simple')
$('#switchViewButton').html('Switch To Advanced View')
resetColumns(simpleColumnFieldMap)

} else {
$('#switchViewButton').val('advanced')
$('#switchViewButton').html('Switch To Simple View')
resetColumns(advancedColumnFieldMap)
}
$('.selectpicker').selectpicker('refresh')
}

createDataTable(data)
function fetchSimpleView(data) {
$('#formSearchTable').hide();
$('#formSearchTable_wrapper').hide();
$('#simpleView').show();
$('#columnPicker').selectpicker('show')
$('#fieldPicker').selectpicker('show')
$('#orderPicker').selectpicker('show')
$('#sortByButton').show()

$('#simpleView').DataTable({
responsive: true,
destroy: true,
searching: false, // we may want to enable this at some point, think it may require custom logic on our end, though.
processing: true,
serverSide: true,
paging: true,
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
pageLength: 50,
columnDefs: [{
// this disables built in ordering on columns with these IDs
// (may be a way to do without specifying each individually but idk)
targets: [0],
orderable: false,
}],
ajax: {
// we fetch the data and do the ordering server side which means all logic is done
// in Python and the datatable just displays the results
url: "/",
type: "POST",
data: { 'data': data },
dataSrc: "data",
}
});
}

function createDataTable(data) {
$("#formSearchAccordion").accordion({ collapsible: true, active: false });
$("#download").prop('disabled', false);
$('#formSearchTable').show();
$('#simpleView').hide()
$('#simpleView_wrapper').hide();
$('#columnPicker').selectpicker('show')
$('#fieldPicker').selectpicker('show')
$('#orderPicker').selectpicker('show')
Expand All @@ -204,7 +304,7 @@ function createDataTable(data) {
serverSide: true,
paging: true,
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
pageLength: 25,
pageLength: 50,
columnDefs: [{
// this disables built in ordering on columns with these IDs
// (may be a way to do without specifying each individually but idk)
Expand Down
Loading

0 comments on commit d2acb2e

Please sign in to comment.