diff --git a/admin/graduationManagement b/admin/graduationManagement new file mode 100644 index 000000000..1eb9bdcbd --- /dev/null +++ b/admin/graduationManagement @@ -0,0 +1,39 @@ +{% set title = "Graduation Management" %} +{% extends "base.html" %} + +{% block scripts %} + {{super()}} + + + + {% block styles %} + {{super()}} + + + {% endblock %} +{% endblock %} + +{% block app_content %} +

Graduation Management

+

Graduating/Graduated Students

+ + + + + + + + + + {% for user in users %} + + + + + + + {%endfor%} + +
NameGraduatedClass Level
{{ user.username }}{{ user.hasGraduated }}{{ user.classLevel }}
+ +{% endblock %} \ No newline at end of file diff --git a/app/controllers/admin/__init__.py b/app/controllers/admin/__init__.py index 375c695bd..3b2b83790 100644 --- a/app/controllers/admin/__init__.py +++ b/app/controllers/admin/__init__.py @@ -11,4 +11,5 @@ from app.controllers.admin import userManagement from app.controllers.admin import volunteers from app.controllers.admin import minor +from app.controllers.admin import graduationManagement diff --git a/app/controllers/admin/graduationManagement.py b/app/controllers/admin/graduationManagement.py new file mode 100644 index 000000000..7a3da2b2d --- /dev/null +++ b/app/controllers/admin/graduationManagement.py @@ -0,0 +1,72 @@ +from flask import render_template, g, abort, request, redirect, url_for, flash, send_file +from app.models.user import User +from app.controllers.admin import admin_bp +from app.logic.bonner import getBonnerCohorts +from app.models.bonnerCohort import BonnerCohort + +from app.logic.graduationManagement import getGraduatedStudent, removeGraduatedStudent, makeGraduatedXls +from app.logic.minor import getMinorProgress + + +@admin_bp.route('/admin/graduationManagement', methods=['GET']) +def gradManagement(): + + if not g.current_user.isAdmin: + abort(403) + + users = User.select(User.username, User.hasGraduated, User.classLevel, User.firstName, User.lastName).where(User.classLevel=='Senior') + + CCEusers = getMinorProgress() + + bonnercohorts = getBonnerCohorts() + + return render_template('/admin/graduationManagement.html', + users = users, + bonnercohorts = bonnercohorts, + CCEstudents = CCEusers) + + +@admin_bp.route('//CheckIfGraduated/', methods=['POST']) +def CheckIfGraduated(username): + """ + This function + username: unique value of a user to correctly identify them + """ + try: + success = getGraduatedStudent(username) + if success: + return "", 200 + else: + return "", 500 + + except Exception as e: + print(e) + return "Error Updating Graduation Status", 500 + +@admin_bp.route('//CheckIfNotGraduated/', methods=['POST']) +def CheckIfNotGraduated(username): + """ + This function removes + username: unique value of a user to correctly identify them + """ + try: + removed = removeGraduatedStudent(username) + if removed: + flash("Graduation status has been updated!", "success") + return "", 200 + else: + flash("Error!", "Failed to update graduation status.") + return "", 500 + except Exception as e: + print(e) + return "Error Updating Graduation Status", 500 + +@admin_bp.route("/gradStudentsxls/", methods=['GET']) +def GradsXls(filterType): + if not g.current_user.isCeltsAdmin: + abort(403) + print(filterType, '#####') + # filterType = request.args.get('filterType', 'all') + newfile = makeGraduatedXls(filterType) + return send_file(open(newfile, 'rb'), download_name='GraduatedStudents.xlsx', as_attachment=True) + diff --git a/app/logic/graduationManagement.py b/app/logic/graduationManagement.py new file mode 100644 index 000000000..ee769041e --- /dev/null +++ b/app/logic/graduationManagement.py @@ -0,0 +1,104 @@ +from collections import defaultdict +from typing import List, Dict +from playhouse.shortcuts import model_to_dict +from peewee import JOIN, fn, Case, DoesNotExist +import xlsxwriter +from app import app + +from app.models.user import User +from app.logic.minor import getMinorProgress +from app.logic.bonner import getBonnerCohorts +from app.models.bonnerCohort import BonnerCohort +from app.models.term import Term + + +def getGraduatedStudent(username): + """ + This function marks students as graduated + Parameters: + username: username of the user graduating + """ + gradStudent = User.get(User.username == username) + if gradStudent: + gradStudent.hasGraduated = True + gradStudent.save() + return True + return False + +def removeGraduatedStudent(username): + """ + This function unmarks students as graduated + Parameters: + username: username of the user graduating + + """ + notGradStudent = User.get(User.username == username) + if notGradStudent: + notGradStudent.hasGraduated = False + notGradStudent.save() + return True + return False + +def makeGraduatedXls(filterType): + """ + Create and save a GraduatedStudent.xlsx file with all of the graduated students. + Working with XLSX files: https://xlsxwriter.readthedocs.io/index.html + + Returns: + The file path and name to the newly created file, relative to the web root. + """ + + print('filtertype:' , filterType, "#####") + + + CCEusers = getMinorProgress() + bonnercohorts = getBonnerCohorts() + + filepath = app.config['files']['base_path'] + '/GraduatedStudents.xlsx' + workbook = xlsxwriter.Workbook(filepath, {'in_memory': True}) + worksheet = workbook.add_worksheet('students') + bold = workbook.add_format({'bold': True}) + + worksheet.write('A1', 'Graduated Students', bold) + worksheet.set_column('A:A', 20) + prev_year = 1 + row = 1 + + if filterType == 'all': + students = User.select().where(User.hasGraduated == True) + elif filterType == 'cce': + students = [student for student in CCEusers if student['hasGraduated']] + # elif filterType == 'bonner': + # students = BonnerCohort.select(BonnerCohort, User).join(User).where(User.hasGraduated == True) + + # print('##### Student list') + + # for name in User.select(User.username): + + # print(name) + + # print('##### Student list') + # print('bonner filter selected #####') + # elif filterType == 'bonnercohorts': + # students = [student for student in bonnercohorts if student['hasGraduated']] + else: + students = User.select() + + for student in students: + # if filterType == 'bonner' and prev_year != student.year: + # row += 1 + # prev_year = student.year + # worksheet.write(row, 0, f"{student.year} - {student.year+1}", bold) + + if filterType == 'cce': + worksheet.write(row, 0, f"{student['firstName']} {student['lastName']}") + print('CCE student found #####') + else: + worksheet.write(row, 0, f"{student.firstName} {student.lastName}") + print(' (all) student found #####') + + row += 1 + + workbook.close() + + return filepath \ No newline at end of file diff --git a/app/static/css/graduationManagement.css b/app/static/css/graduationManagement.css new file mode 100644 index 000000000..875355703 --- /dev/null +++ b/app/static/css/graduationManagement.css @@ -0,0 +1,33 @@ +#selectAll-container { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-bottom: 15px; +} + +.btn-group { + margin-right: 10px; /* Space between the dropdown and the buttons */ + +} + +h1 { + margin-bottom: 0px; + margin-top: 10px; +} + +.container { + margin-bottom: 20px; /* Adjust this as needed for proper spacing */ +} + +.w-25{ + margin-bottom: 50px; + + } + + +#cohortFilter{ + position:absolute; + right: 170px; + top: -19px; + +} diff --git a/app/static/js/graduationManagement.js b/app/static/js/graduationManagement.js new file mode 100644 index 000000000..99c18e77f --- /dev/null +++ b/app/static/js/graduationManagement.js @@ -0,0 +1,193 @@ +$(document).ready(function() { + var gradStudentsTable = $('#gradStudentsTable').DataTable({ + paging: true, + searching: true, + info: true + }); + let selectAllMode = true; + + $('.alert').alert('close'); + + $('.dropdown-item').click(function() { + var filterType = $(this).data('filter'); + var buttonText = $(this).text(); + + $('#main-filter').first().text(buttonText); + $('#cohortFilter').text('Bonner Cohort'); + + + + $('#exportFile').attr('href', `/gradStudentsxls/${filterType}`); + + if (filterType === 'all') { + gradStudentsTable.search('').draw(); + gradStudentsTable.rows().every(function() { + $(this.node()).show(); + }); + gradStudentsTable.draw(); + + } else if (filterType === 'bonner' ) { + gradStudentsTable.rows().every(function() { + var studentType = $(this.node()).data('student-type'); + if (studentType === 'bonner') { + $(this.node()).show(); + } else { + $(this.node()).hide(); + } + }); + gradStudentsTable.draw(); + + } else if (filterType === 'cce') { + var cceUsers = $(this).data('cce'); + + const sanitizedString = cceUsers + .replace(/'/g, '"') + .replace(/False/g, 'false') + .replace(/Decimal\('(\d+)'\)/g, '$1'); + + const CCElist = {}; + + const userItems = sanitizedString.slice(1, -1).split('}, {'); + + userItems.forEach(item => { + + item = item.replace(/[{}/]/g, '').trim(); + + + const pairs = item.split(', '); + + let username, engagementCount; + + pairs.forEach(pair => { + const [key, value] = pair.split(': '); + if (key.trim() === '"username"') { + username = value.replace(/"/g, ''); + } else if (key.trim() === '"engagementCount"') { + engagementCount = parseFloat(value[9]); + + } + }); + + + if (username && engagementCount !== undefined) { + CCElist[username] = engagementCount; + } + }); + + + gradStudentsTable.rows().every(function() { + var studentUserName = $(this.node()).data('username'); + for ( const [key, value] of Object.entries(CCElist)){ + var username = key; + if ( studentUserName == username && CCElist[key] > 0 ) { + $(this.node()).show(); + break; + } else { + $(this.node()).hide(); + } + } + }); + gradStudentsTable.draw(); + } + }); + + $('.dropdown-item-new').click(function() { + + var cohortusers = $(this).data('cohort-users'); + var buttonText = $(this).text(); + + $('#cohortFilter').text(buttonText); + $('#main-filter').first().text('All'); + + $('#selectAll').text('Select All'); + selectAllMode = true + + gradStudentsTable.rows().every(function(){ + $(this.node()).hide(); + }) + const cleanedString = cohortusers + .replace(/^\[|\]$/g, '') + .replace(//g, '') + .trim(); + + const CohortArray = cleanedString.split(',').map(user => user.trim()); + + gradStudentsTable.rows().every(function() { + var studentUserName = $(this.node()).data('username'); + + for ( let i = 0 ; i < CohortArray.length ; i++){ + + var studentType = $(this.node()).data('student-type'); + if (studentType === 'bonner' && studentUserName == CohortArray[i]) { + $(this.node()).show(); + break; + } else { + $(this.node()).hide(); + } + } + }); + gradStudentsTable.draw(); + }); + +$('.graduated-checkbox').change(function() { + let hasGraduated = $(this).is(':checked'); + let username = $(this).data('username'); + let routeUrl = hasGraduated ? "hasGraduated" : "hasNotGraduated"; + let graduationURL = "/" + username + "/" + routeUrl + "/"; + + $.ajax({ + type: "POST", + url: graduationURL, + success: function(response) { + + if ($('.alert').length >= 1 ){ + + $('.alert').alert('close'); + }; + console.log("Graduation status updated successfully!"); + msgFlash("Graduation status updated successfully!", "success"); + MessageDelay() + }, + error: function(status, error) { + msgFlash("Error updating graduation status.", "error"); + console.error("Error updating graduation status:", error); + MessageDelay() + } + }); +}); + + + $('#selectAll').click(function() { + + if (selectAllMode) { + gradStudentsTable.rows().every(function() { + var rowNode = this.node(); + if ($(rowNode).is(':visible')) { + $(rowNode).find('.graduated-checkbox').prop('checked', true ).change(); + } + }); + $(this).text('Deselect All'); + } else { + + gradStudentsTable.rows().every(function() { + var rowNode = this.node(); + if ($(rowNode).is(':visible')) { + $(rowNode).find('.graduated-checkbox').prop('checked', false ).change(); + } + }); + $(this).text('Select All'); + } + selectAllMode = !selectAllMode; + }); +}); + +function MessageDelay(){ + + return setTimeout(FadeMessage,5000) +} + +function FadeMessage(){ + if ($('.alert').length > 0 ){ + $('.alert').fadeOut('fast'); + }; +} \ No newline at end of file diff --git a/app/static/js/sidebar.js b/app/static/js/sidebar.js index 1ac368671..18e496318 100644 --- a/app/static/js/sidebar.js +++ b/app/static/js/sidebar.js @@ -1,5 +1,7 @@ $(document).ready(function() { + $('.alert').alert('close');// close excess flash messages from graduation managment + // fetch the number of interested students and unapproved courses and display them in the sidebar if there are any // and add hovers to describe what the numbers we are adding to the sidebar mean. $.ajax({ diff --git a/app/templates/admin/graduationManagement.html b/app/templates/admin/graduationManagement.html new file mode 100644 index 000000000..5818a477e --- /dev/null +++ b/app/templates/admin/graduationManagement.html @@ -0,0 +1,77 @@ +{% set title = "Graduation Management" %} +{% extends "base.html" %} + + + +{% block scripts %} + {{super()}} + + + + + + + {% block styles %} + {{super()}} + + + + {% endblock %} +{% endblock %} + + + + + +{% block app_content %} +

Graduation Managment

+
+
+
+ + +
+
+ + +
+ + + + +
+ + + + + + + + + + + {% for user in users %} + + + + + + + {%endfor%} + +
NameGraduatedClass Level
{{ user.firstName }} {{ user.lastName }} + {{ user.classLevel }}
+ +{% endblock %} \ No newline at end of file diff --git a/app/templates/sidebar.html b/app/templates/sidebar.html index a1f2f7a37..8c6c1505c 100644 --- a/app/templates/sidebar.html +++ b/app/templates/sidebar.html @@ -75,6 +75,9 @@ Minor Management + + Graduation Management + Reports diff --git a/database/tmp-backups/2024-11-15-backup.sql b/database/tmp-backups/2024-11-15-backup.sql new file mode 100644 index 000000000..e69de29bb