From 7eaaecd2e430398b355498067c138ca615b4f662 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Fri, 10 Jul 2026 17:29:47 -0400 Subject: [PATCH 01/31] Built signature page and updated profile to show signature status --- app/controllers/admin/routes.py | 17 +++++++ app/controllers/main/routes.py | 10 ++++ app/models/user.py | 1 + app/static/css/handbookSignature.css | 12 +++++ app/static/css/userProfile.css | 35 +++++++++++++ app/static/js/handbookSignature.js | 73 ++++++++++++++++++++++++++++ app/static/js/searchStudent.js | 2 +- app/templates/admin/signature.html | 59 ++++++++++++++++++++++ app/templates/main/userProfile.html | 12 ++++- database/test_data.py | 8 ++- 10 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 app/static/css/handbookSignature.css create mode 100644 app/static/js/handbookSignature.js create mode 100644 app/templates/admin/signature.html diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 8d24ffdf6..aab87ae74 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -682,3 +682,20 @@ def displayEventFile(): isChecked = fileData.get('checked') == 'true' eventfile.changeDisplay(fileData['id'], isChecked) return "" + +@admin_bp.route("/handbookSignature", methods=["GET", "POST"]) +def handbookSignature(): + if request.method == "GET": + if not g.current_user.isAdmin: + abort(403) + + return render_template("admin/signature.html") + else: + data = request.form + + signer = User.get(User.username == data["studentID"]) + if signer: + signer.lastHandbookSignature = datetime.now().strftime("%Y-%m-%d") + signer.save() + + return "", 200 \ No newline at end of file diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 6f83f32dd..2b117b936 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -4,6 +4,8 @@ from http import cookies from playhouse.shortcuts import model_to_dict from flask import request, render_template, jsonify, g, abort, flash, redirect, url_for, make_response, session, request +from dateutil.relativedelta import relativedelta + from app.controllers.main import main_bp from app import app @@ -234,6 +236,13 @@ def viewUsersProfile(username): managersList = [id[1] for id in managersProgramDict.items()] totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) + handbookOverdue = True + if volunteer.lastHandbookSignature: + if volunteer.lastHandbookSignature > (datetime.datetime.now() - relativedelta(years=1)).date(): + handbookOverdue = False + volunteer.lastHandbookSignature = volunteer.lastHandbookSignature.strftime("%m/%d/%Y") + else: + volunteer.lastHandbookSignature = "NOT SIGNED" return render_template ("/main/userProfile.html", username=username, programs = programs, @@ -252,6 +261,7 @@ def viewUsersProfile(username): managersList = managersList, participatedInLabor = getCeltsLaborHistory(volunteer), totalSustainedEngagements = totalSustainedEngagements, + handbookOverdue = handbookOverdue ) abort(403) diff --git a/app/models/user.py b/app/models/user.py index c9652f86c..d0c2ae716 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -19,6 +19,7 @@ class User(baseModel): minorInterest = BooleanField(null=True) hasGraduated = BooleanField(default=False) declaredMinor = BooleanField(default=False) + lastHandbookSignature = DateField(null=True) # override BaseModel's __init__ so that we can set up an instance attribute for cache def __init__(self,*args, **kwargs): diff --git a/app/static/css/handbookSignature.css b/app/static/css/handbookSignature.css new file mode 100644 index 000000000..724ac7515 --- /dev/null +++ b/app/static/css/handbookSignature.css @@ -0,0 +1,12 @@ +canvas.disabled { + pointer-events: none; + opacity: 0.6; + cursor: not-allowed; +} + +canvas { + transition: all 0.3s ease; + width: 100%; + height: 200px; + border: 1px solid #ddd; +} diff --git a/app/static/css/userProfile.css b/app/static/css/userProfile.css index 561e2fc7f..0fba5cd56 100644 --- a/app/static/css/userProfile.css +++ b/app/static/css/userProfile.css @@ -39,3 +39,38 @@ div.profile-links a:not(:first-child) { position: relative; left: 12px; width: 95%; } +.inline-wrapper { + white-space: nowrap; +} + +/* Container holds both elements */ +.tooltip-container { + position: relative; + display: inline-flex; + align-items: center; + cursor: pointer; + margin-left: 4px; /* Adds a small gap after the text */ +} + +/* Hidden by default and positioned to the right */ +.tooltip-text { + visibility: hidden; + position: absolute; + top: 50%; /* Aligns top edge to the middle of the icon */ + left: 115%; /* Pushes the text box completely to the right of the icon */ + transform: translateY(-50%); /* Perfectly centers the text box vertically */ + background-color: #333; + color: #fff; + padding: 8px; + border-radius: 4px; + white-space: nowrap; + font-size: 14px; + opacity: 0; + transition: opacity 0.3s; +} + +/* Show the text on hover */ +.tooltip-container:hover .tooltip-text { + visibility: visible; + opacity: 1; +} \ No newline at end of file diff --git a/app/static/js/handbookSignature.js b/app/static/js/handbookSignature.js new file mode 100644 index 000000000..135217366 --- /dev/null +++ b/app/static/js/handbookSignature.js @@ -0,0 +1,73 @@ +var canvas = $('#handbook-signature-pad')[0]; +var signaturePad = new SignaturePad(canvas); + +// Resize canvas to fix Bootstrap 3 responsiveness +function resizeCanvas() { + var ratio = Math.max(window.devicePixelRatio || 1, 1); + canvas.width = canvas.offsetWidth * ratio; + canvas.height = canvas.offsetHeight * ratio; + canvas.getContext("2d").scale(ratio, ratio); + signaturePad.clear(); // Clear it out to accommodate the new size +} + +window.addEventListener("resize", resizeCanvas); +resizeCanvas(); + +// Button Actions +$('#clear-btn').on('click', function () { + signaturePad.clear(); +}); + + +$('#save-btn').on('click', function () { + console.log(":"+$('#searchStudentsInput').val()+":") + if (!$('#searchStudentsInput').val()) { + alert("Select a student!") + } else if ($('.checkbox:checked').length != $('.checkbox').length) { + alert("Please read and agree to all the statements.") + } else if (signaturePad.isEmpty()) { + alert("Please provide a signature first."); + } else { + var dataURL = signaturePad.toDataURL(); + console.log(dataURL); // You can send this base64 string to your server + alert("Thank you for signing the CELTS Handbook. We look forward to your participation in CELTS!"); + $.ajax({ + url: `/handbookSignature`, + type: "POST", + data: {studentID: $('#handbook-signature-pad').attr('data-student')}, + success: function(s){ + console.log("Saved!") + }, + error: function(error, status){ + console.log(error, status) + } + }) + } +}); + +var searchResult = null; +import searchUser from './searchUser.js' +function callback(selected) { + // don't do anything with the search results +} + +$(document).ready(function() { + $("#searchStudentsInput").on("input", function() { + searchResult = searchUser("searchStudentsInput", callback); + }); + + $("#searchStudentsInput").change(function() { + $('#handbook-signature-pad').attr('data-student', $(this).val()); + }); + + $(".checkboxes").on("change", function() { + if ($('.checkbox:checked').length === $('.checkbox').length) { + $('#handbook-signature-pad').removeClass("disabled"); + } else { + $('#handbook-signature-pad').addClass("disabled"); + signaturePad.clear(); + } + }); + + +}); \ No newline at end of file diff --git a/app/static/js/searchStudent.js b/app/static/js/searchStudent.js index 93062f6de..b4d0dcad3 100644 --- a/app/static/js/searchStudent.js +++ b/app/static/js/searchStudent.js @@ -1,6 +1,6 @@ import searchUser from './searchUser.js' function callback(selected) { - $("#searchStudent").submit(); + $("#searchStudentsInput").submit(); } $(document).ready(function() { $("#searchStudentsInput").on("input", function() { diff --git a/app/templates/admin/signature.html b/app/templates/admin/signature.html new file mode 100644 index 000000000..9e11bb273 --- /dev/null +++ b/app/templates/admin/signature.html @@ -0,0 +1,59 @@ +{% set title = "Handbook Signature" %} +{% extends "base.html" %} + +{% block scripts %} + {{super()}} + + +{% endblock %} + +{% block styles %} + {{super()}} + +{% endblock %} + +{% block app_content %} +
+
+

Handbook Acknowledgement Form

+
+
+
+
+
+
+
+ + +
+
+
+
+ +
+ +
+
+ +
+ {% if g.current_user.hasCurrentCeltsLabor %} +
+ +
+ {% endif %} + +
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 13fafda69..913443dc9 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -18,11 +18,19 @@

{{volunteer.firstName}} {{volunteer.lastName}}

-
+
{{volunteer.bnumber}}
{{volunteer.email}}
+
CELTS Handbook signed on: {{volunteer.lastHandbookSignature}} + {% if handbookOverdue -%} +
+ + See a Program Manager or CELTS staff member to sign the handbook +
+ {% endif %} +
-
+
{% if volunteer.major -%}
{{volunteer.major}}
{% endif %} diff --git a/database/test_data.py b/database/test_data.py index 847bc2a4d..eabf96f0a 100644 --- a/database/test_data.py +++ b/database/test_data.py @@ -1613,9 +1613,15 @@ }, { "user": "ayisie", - "positionTitle": "AGP Team Memeber", + "positionTitle": "AGP Team Member", "term": 2, "isAcademicYear": True + }, + { + "user": "neillz", + "positionTitle": "AGP Team Leader", + "term": 3, + "isAcademicYear": True } ] CeltsLabor.insert_many(celtsLabor).on_conflict_replace().execute() \ No newline at end of file From e30170e5f9363fe7e2f2e99040bf6ff017bb7e17 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Sun, 12 Jul 2026 20:58:25 -0400 Subject: [PATCH 02/31] Modifying location and usage of signature for handbook --- app/controllers/admin/routes.py | 18 +++--- app/controllers/main/routes.py | 7 ++- app/static/css/handbookSignature.css | 18 ++---- app/static/js/handbookSignature.js | 91 +++++++++++----------------- app/static/js/userProfile.js | 2 +- app/templates/main/userProfile.html | 27 +++++++-- 6 files changed, 77 insertions(+), 86 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index aab87ae74..2e26d5738 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -1,6 +1,6 @@ from flask import request, render_template, url_for, g, redirect from flask import flash, abort, jsonify, session, send_file -from peewee import DoesNotExist, fn, IntegrityError +from peewee import DoesNotExist, IntegrityError from playhouse.shortcuts import model_to_dict import json from datetime import datetime @@ -27,6 +27,7 @@ from app.models.term import Term from app.models.eventViews import EventView from app.models.courseStatus import CourseStatus +from app.models.programManager import ProgramManager from app.logic.userManagement import getAllowedPrograms, getAllowedTemplates from app.logic.createLogs import createActivityLog @@ -685,15 +686,12 @@ def displayEventFile(): @admin_bp.route("/handbookSignature", methods=["GET", "POST"]) def handbookSignature(): - if request.method == "GET": - if not g.current_user.isAdmin: - abort(403) - - return render_template("admin/signature.html") - else: - data = request.form - - signer = User.get(User.username == data["studentID"]) + if request.method == "GET": + return render_template("admin/signature.html") + # POST + else: + signer = User.get(User.username == g.current_user.username) + print("CURRENT USER: ", signer) if signer: signer.lastHandbookSignature = datetime.now().strftime("%Y-%m-%d") signer.save() diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 2b117b936..118cc7cdc 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -238,9 +238,12 @@ def viewUsersProfile(username): handbookOverdue = True if volunteer.lastHandbookSignature: - if volunteer.lastHandbookSignature > (datetime.datetime.now() - relativedelta(years=1)).date(): + today = datetime.date.today() + + # Signature must be during the current academic year (July 1 - June 30) + if datetime.date(today.year, 7, 1) < volunteer.lastHandbookSignature < datetime.date(today.year + 1, 6, 30): handbookOverdue = False - volunteer.lastHandbookSignature = volunteer.lastHandbookSignature.strftime("%m/%d/%Y") + volunteer.lastHandbookSignature = volunteer.lastHandbookSignature.strftime("%m/%d/%Y") else: volunteer.lastHandbookSignature = "NOT SIGNED" return render_template ("/main/userProfile.html", diff --git a/app/static/css/handbookSignature.css b/app/static/css/handbookSignature.css index 724ac7515..39893ece4 100644 --- a/app/static/css/handbookSignature.css +++ b/app/static/css/handbookSignature.css @@ -1,12 +1,6 @@ -canvas.disabled { - pointer-events: none; - opacity: 0.6; - cursor: not-allowed; -} - -canvas { - transition: all 0.3s ease; - width: 100%; - height: 200px; - border: 1px solid #ddd; -} +#handbook-signature-pad { + width: 100%; + height: 200px; /* pick whatever fits comfortably in the modal */ + border: 1px solid #696969; + touch-action: none; /* prevents scroll/gesture conflicts while signing */ +} \ No newline at end of file diff --git a/app/static/js/handbookSignature.js b/app/static/js/handbookSignature.js index 135217366..75a816002 100644 --- a/app/static/js/handbookSignature.js +++ b/app/static/js/handbookSignature.js @@ -3,71 +3,52 @@ var signaturePad = new SignaturePad(canvas); // Resize canvas to fix Bootstrap 3 responsiveness function resizeCanvas() { - var ratio = Math.max(window.devicePixelRatio || 1, 1); - canvas.width = canvas.offsetWidth * ratio; - canvas.height = canvas.offsetHeight * ratio; + var ratio = Math.max(window.devicePixelRatio || 1, 1); + var displayWidth = canvas.offsetWidth; + var displayHeight = canvas.offsetHeight; + canvas.width = displayWidth * ratio; + canvas.height = displayHeight * ratio; + // Lock the on-page size so it doesn't visually grow with the buffer + canvas.style.width = displayWidth + "px"; + canvas.style.height = displayHeight + "px"; canvas.getContext("2d").scale(ratio, ratio); - signaturePad.clear(); // Clear it out to accommodate the new size + signaturePad.clear(); } window.addEventListener("resize", resizeCanvas); -resizeCanvas(); +$('#editVolunteerModal').on('shown.bs.modal', function () { + resizeCanvas(); +}); + +$('#editVolunteerModal').on('hidden.bs.modal', function () { + $("#signatureHeader").remove(); + canvas.remove(); +}); -// Button Actions $('#clear-btn').on('click', function () { signaturePad.clear(); }); $('#save-btn').on('click', function () { - console.log(":"+$('#searchStudentsInput').val()+":") - if (!$('#searchStudentsInput').val()) { - alert("Select a student!") - } else if ($('.checkbox:checked').length != $('.checkbox').length) { - alert("Please read and agree to all the statements.") - } else if (signaturePad.isEmpty()) { - alert("Please provide a signature first."); - } else { - var dataURL = signaturePad.toDataURL(); - console.log(dataURL); // You can send this base64 string to your server - alert("Thank you for signing the CELTS Handbook. We look forward to your participation in CELTS!"); - $.ajax({ - url: `/handbookSignature`, - type: "POST", - data: {studentID: $('#handbook-signature-pad').attr('data-student')}, - success: function(s){ - console.log("Saved!") - }, - error: function(error, status){ - console.log(error, status) - } - }) - } -}); - -var searchResult = null; -import searchUser from './searchUser.js' -function callback(selected) { - // don't do anything with the search results -} - -$(document).ready(function() { - $("#searchStudentsInput").on("input", function() { - searchResult = searchUser("searchStudentsInput", callback); - }); - - $("#searchStudentsInput").change(function() { - $('#handbook-signature-pad').attr('data-student', $(this).val()); - }); - - $(".checkboxes").on("change", function() { - if ($('.checkbox:checked').length === $('.checkbox').length) { - $('#handbook-signature-pad').removeClass("disabled"); - } else { - $('#handbook-signature-pad').addClass("disabled"); - signaturePad.clear(); + var dataURL = signaturePad.toDataURL(); + console.log(dataURL); // You can send this base64 string to your server + $.ajax({ + url: `/handbookSignature`, + type: "POST", + data: {studentID: $('#handbook-signature-pad').attr('data-student')}, + success: function(s){ + alert("Thank you for signing the CELTS Handbook. We look forward to your participation in CELTS!"); + signaturePad.off(); + $('#save-btn').hide(); + $('#clear-btn').hide(); + const today = new Date(); + $("#signatureDate").text(today.toLocaleDateString('en-US')); + $("#signatureDate").css("color", "black"); + $("#handbookSignatureContainer .bi-info-circle-fill").hide(); + }, + error: function(error, status){ + console.log(error, status) } - }); - - + }) }); \ No newline at end of file diff --git a/app/static/js/userProfile.js b/app/static/js/userProfile.js index 61af76787..293b68592 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -443,4 +443,4 @@ function updateManagers(el, volunteerUsername ) { console.log(error, status) } }) -} +} \ No newline at end of file diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 913443dc9..d3a20a1fb 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -5,11 +5,14 @@ {{super()}} + + {% endblock %} {% block styles %} {{super()}} + {##} {% endblock %} {% block app_content %} @@ -17,11 +20,11 @@

{{volunteer.firstName}} {{volunteer.lastName}}

-
+
{{volunteer.bnumber}}
{{volunteer.email}}
-
CELTS Handbook signed on: {{volunteer.lastHandbookSignature}} +
CELTS Handbook signed on: {{volunteer.lastHandbookSignature}} {% if handbookOverdue -%}
@@ -707,11 +710,23 @@
Dietary Restrictions
-
- - +
+ +
+
+ +
{% endif %}
From 9095cecc0fea9806fcb68681e8b263bfe9101b64 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Sun, 12 Jul 2026 21:08:29 -0400 Subject: [PATCH 04/31] Removed unnecessary GET route --- app/controllers/admin/routes.py | 22 +++++------ app/templates/admin/signature.html | 59 ------------------------------ 2 files changed, 9 insertions(+), 72 deletions(-) delete mode 100644 app/templates/admin/signature.html diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 6b8707081..207e00d08 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -684,16 +684,12 @@ def displayEventFile(): eventfile.changeDisplay(fileData['id'], isChecked) return "" -@admin_bp.route("/handbookSignature", methods=["GET", "POST"]) -def handbookSignature(): - if request.method == "GET": - return render_template("admin/signature.html") - # POST - else: - signer = User.get(User.username == g.current_user.username) - print("CURRENT USER: ", signer) - if signer: - signer.lastHandbookSignature = datetime.now().strftime("%Y-%m-%d") - signer.save() - - return "", 200 \ No newline at end of file +@admin_bp.route("/handbookSignature", methods=["POST"]) +def handbookSignature(): + signer = User.get(User.username == g.current_user.username) + print("CURRENT USER: ", signer) + if signer: + signer.lastHandbookSignature = datetime.now().strftime("%Y-%m-%d") + signer.save() + + return "", 200 \ No newline at end of file diff --git a/app/templates/admin/signature.html b/app/templates/admin/signature.html deleted file mode 100644 index 9e11bb273..000000000 --- a/app/templates/admin/signature.html +++ /dev/null @@ -1,59 +0,0 @@ -{% set title = "Handbook Signature" %} -{% extends "base.html" %} - -{% block scripts %} - {{super()}} - - -{% endblock %} - -{% block styles %} - {{super()}} - -{% endblock %} - -{% block app_content %} -
-
-

Handbook Acknowledgement Form

-
-
-
-
-
-
-
- - -
-
-
-
- -
- -
-
- -
- {% if g.current_user.hasCurrentCeltsLabor %} -
- -
- {% endif %} - -
- - - -
-{% endblock %} \ No newline at end of file From 8d9777eff91154868c63529997ea8bc0a62e6c47 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Sun, 12 Jul 2026 21:14:40 -0400 Subject: [PATCH 05/31] Now only the user can sign their own profile --- app/controllers/main/routes.py | 24 ++++++++++++++---------- app/templates/main/userProfile.html | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 118cc7cdc..7e1819442 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -236,16 +236,7 @@ def viewUsersProfile(username): managersList = [id[1] for id in managersProgramDict.items()] totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) - handbookOverdue = True - if volunteer.lastHandbookSignature: - today = datetime.date.today() - - # Signature must be during the current academic year (July 1 - June 30) - if datetime.date(today.year, 7, 1) < volunteer.lastHandbookSignature < datetime.date(today.year + 1, 6, 30): - handbookOverdue = False - volunteer.lastHandbookSignature = volunteer.lastHandbookSignature.strftime("%m/%d/%Y") - else: - volunteer.lastHandbookSignature = "NOT SIGNED" + handbookOverdue = getHandbookStatus(volunteer) return render_template ("/main/userProfile.html", username=username, programs = programs, @@ -268,6 +259,19 @@ def viewUsersProfile(username): ) abort(403) +def getHandbookStatus(volunteer): + handbookOverdue = True + if volunteer.lastHandbookSignature: + today = datetime.date.today() + + # Signature must be during the current academic year (July 1 - June 30) + if datetime.date(today.year, 7, 1) < volunteer.lastHandbookSignature < datetime.date(today.year + 1, 6, 30): + handbookOverdue = False + volunteer.lastHandbookSignature = volunteer.lastHandbookSignature.strftime("%m/%d/%Y") + else: + volunteer.lastHandbookSignature = "NOT SIGNED" + return handbookOverdue + @main_bp.route('/profile//emergencyContact', methods=['GET', 'POST']) def emergencyContactInfo(username): """ diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 4468665e8..42d02d3da 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -717,7 +717,7 @@
Dietary Restrictions

- {% if handbookOverdue %} + {% if handbookOverdue and g.current_user.username == volunteer.username%}
Handbook Signature
From 25f41192618c6978ed3f5d176fb9e75323c89cde Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Sun, 12 Jul 2026 21:16:39 -0400 Subject: [PATCH 06/31] removed commented out css file --- app/templates/main/userProfile.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 42d02d3da..da3cdc8cc 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -11,8 +11,7 @@ {% block styles %} {{super()}} - - {##} + {% endblock %} {% block app_content %} From bb1b291bcf105e2283af68765b63e9d0a57f2059 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Sun, 12 Jul 2026 21:18:09 -0400 Subject: [PATCH 07/31] removed extra css file too --- app/static/css/handbookSignature.css | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 app/static/css/handbookSignature.css diff --git a/app/static/css/handbookSignature.css b/app/static/css/handbookSignature.css deleted file mode 100644 index 39893ece4..000000000 --- a/app/static/css/handbookSignature.css +++ /dev/null @@ -1,6 +0,0 @@ -#handbook-signature-pad { - width: 100%; - height: 200px; /* pick whatever fits comfortably in the modal */ - border: 1px solid #696969; - touch-action: none; /* prevents scroll/gesture conflicts while signing */ -} \ No newline at end of file From 678abf6e47bd58320861a34b5c0bd58e32f7bc03 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Sun, 12 Jul 2026 23:12:44 -0400 Subject: [PATCH 08/31] Added Extravaganza page --- app/controllers/main/routes.py | 27 +++++++- app/static/js/extravaganza.js | 24 +++++++ app/templates/macros/programTableMacro.html | 66 ++++++++++++++++++ app/templates/main/extravanganzaWelcome.html | 72 ++++++++++++++++++++ app/templates/main/userProfile.html | 4 +- 5 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 app/static/js/extravaganza.js create mode 100644 app/templates/macros/programTableMacro.html create mode 100644 app/templates/main/extravanganzaWelcome.html diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 7e1819442..a927c5921 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -235,8 +235,8 @@ def viewUsersProfile(username): managersProgramDict = getManagerProgramDict(g.current_user) managersList = [id[1] for id in managersProgramDict.items()] totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) - handbookOverdue = getHandbookStatus(volunteer) + return render_template ("/main/userProfile.html", username=username, programs = programs, @@ -660,3 +660,28 @@ def updateMinorDeclaration(username): tab = request.args.get("tab", "interested") return redirect(url_for('admin.manageMinor', tab=tab)) +@main_bp.route('/extravaganza', methods=['GET']) +def extravaganza(): + + programs = Program.select().where(Program.isOtherCeltsSponsored == False) + interests = Interest.select(Interest, Program).join(Program).where(Interest.user == g.current_user) + programsInterested = [interest.program for interest in interests] + + Term2 = Term.alias() + sameYearTerms = Term.select().join(Term2, on=(Term.academicYear == Term2.academicYear)).where(Term2.isCurrentTerm == True) + print("@@@@@@@@@", list(sameYearTerms), "@@@@@") + upcomingAllVolunteers = Event.select().where(Event.isAllVolunteerTraining, Event.term.in_(sameYearTerms)) + upcomingTrainings = Event.select().where(Event.isTraining, Event.term.in_(sameYearTerms)) + + + + for idx, training in enumerate(upcomingTrainings): + upcomingTrainings[idx].startDate = training.startDate.strftime("%b %d") + upcomingTrainings[idx].timeStart = training.timeStart.strftime("%I:%M %p") + + return render_template("main/extravanganzaWelcome.html", + programs = programs, + programsInterested = programsInterested, + upcomingTrainings = upcomingTrainings, + upcomingAllVolunteers = upcomingAllVolunteers + ) \ No newline at end of file diff --git a/app/static/js/extravaganza.js b/app/static/js/extravaganza.js new file mode 100644 index 000000000..8e90827ad --- /dev/null +++ b/app/static/js/extravaganza.js @@ -0,0 +1,24 @@ +$(document).ready(function(){ + + $(".interestedInput").click(function updateInterest(){ + var programID = $(this).data("programid"); + var username = $(this).data('username'); + + console.log(programID + username); + + var interest = $(this).is(':checked'); + var routeUrl = interest ? "addInterest" : "removeInterest"; + var interestUrl = "/" + username + "/" + routeUrl + "/" + programID ; + $.ajax({ + method: "POST", + url: interestUrl, + success: function(response) { + window.location.reload(); + }, + error: function(request, status, error) { + console.log(status,error); + location.reload(); + } + }); + }); +}); \ No newline at end of file diff --git a/app/templates/macros/programTableMacro.html b/app/templates/macros/programTableMacro.html new file mode 100644 index 000000000..49b458679 --- /dev/null +++ b/app/templates/macros/programTableMacro.html @@ -0,0 +1,66 @@ +{% macro programTable(eligibilityTable, volunteer) %} +
+ + + + + + + {% if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff%} + + {% if g.current_user.isCeltsAdmin%} + + {% endif %} + {% endif %} + + + + + {% for row in eligibilityTable %} + {% set trainingList = row['trainingList'][volunteer.username] %} + {% set checked = "" %} + {% if row.program in programsInterested %} + {% set checked = "checked" %} + {% endif %} + {% from 'macros/trainingsHoverMacro.html' import trainingsHover %} + + + + + {% if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff %} + {% if row.isNotBanned %} + + + {% if g.current_user.isCeltsAdmin %} + + {% endif %} + + + {% endif %} + + {% endfor %} + +
ProgramIndicated InterestedTrainingEligibilityOn Transcript
{{row.program.programName}} + {% set hasCompletedAllTrainings = row.completedTraining %} +    + View + Eligible + {% set label = "Ban" %} + {% else %} + Banned + {% set label = "Unban" %} + {% endif %} + + {% if g.current_user.isCeltsAdmin %} + + {% endif %} + + +
+
+ For more information about CELTS opportunities, click here. +
+ +{% endmacro %} \ No newline at end of file diff --git a/app/templates/main/extravanganzaWelcome.html b/app/templates/main/extravanganzaWelcome.html new file mode 100644 index 000000000..edce3174b --- /dev/null +++ b/app/templates/main/extravanganzaWelcome.html @@ -0,0 +1,72 @@ +{%set title ="Volunteer Extravaganza"%} +{% extends "base.html"%} + +{% block scripts %} + {{super()}} + +{% endblock %} + +{% block styles %} + {{super()}} +{% endblock %} + +{% block app_content %} +

Welcome to the CELTS Volunteer Extravaganza
{{g.current_user.fullName}}!

+

Step 1: Indicate your interest in a CELTS programs below. Your interest helps us get you involved!

+

Step 2: Attend one of the All Volunteers Trainings listed below. Click to RSVP!

+

Step 3: Attend the Program-specific training to learn more about volunteering with that program!

+ +

CELTS Programs

+
+ + + + + +
All programs require attending one All Volunteers Training! + {% for training in upcomingAllVolunteers %} + {% if not loop.first %}
{% endif %} + {{training.name}} ({{training.startDate}} @ {{training.timeStart}}) + {% endfor %} +
+
+
+ + + + + + + + + + {% for program in programs %} + {% if program in programsInterested %} + {% set checked = "checked" %} + {% endif %} + + + + + + {% endfor %} + +
ProgramIndicate InterestUpcoming Trainings
(Required to participate)
{{program.programName}} + + + {% for training in upcomingTrainings %} + {% if training.program.id == program.id %} + {{training.name}} ({{training.startDate}} @ {{training.timeStart}}) + {% endif %} + {% endfor %} +
+ For more information about CELTS opportunities, click here. +
+ + +{% endblock %} diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index da3cdc8cc..b1563d773 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -196,7 +196,7 @@
You have not participated in any events.
- +
@@ -275,7 +275,7 @@

- + {% if participatedInLabor or volunteer.isCeltsStudentStaff%} From 72c564f77cddcf2fce120159ac4641a5cddf3b25 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Mon, 13 Jul 2026 11:19:16 -0400 Subject: [PATCH 09/31] controller checks for studentID from ajax request --- app/controllers/admin/routes.py | 5 ++++- app/controllers/main/routes.py | 3 +-- app/logic/loginManager.py | 2 +- app/static/js/handbookSignature.js | 8 ++++---- app/templates/main/userProfile.html | 4 +++- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 207e00d08..ad798ee27 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -685,7 +685,10 @@ def displayEventFile(): return "" @admin_bp.route("/handbookSignature", methods=["POST"]) -def handbookSignature(): +def handbookSignature(): + data = request.get_json() + if not (g.current_user.username == data.get('studentID')): + abort(403) signer = User.get(User.username == g.current_user.username) print("CURRENT USER: ", signer) if signer: diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index a927c5921..1e4fe689d 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -661,8 +661,7 @@ def updateMinorDeclaration(username): return redirect(url_for('admin.manageMinor', tab=tab)) @main_bp.route('/extravaganza', methods=['GET']) -def extravaganza(): - +def extravaganza(): programs = Program.select().where(Program.isOtherCeltsSponsored == False) interests = Interest.select(Interest, Program).join(Program).where(Interest.user == g.current_user) programsInterested = [interest.program for interest in interests] diff --git a/app/logic/loginManager.py b/app/logic/loginManager.py index f21a8815e..1348c5803 100644 --- a/app/logic/loginManager.py +++ b/app/logic/loginManager.py @@ -53,4 +53,4 @@ def getLoginUser(): return user def getCurrentTerm(): - return Term.get_or_none(isCurrentTerm = True) + return Term.get_or_none(isCurrentTerm = True) \ No newline at end of file diff --git a/app/static/js/handbookSignature.js b/app/static/js/handbookSignature.js index 75a816002..04b903c28 100644 --- a/app/static/js/handbookSignature.js +++ b/app/static/js/handbookSignature.js @@ -16,6 +16,7 @@ function resizeCanvas() { } window.addEventListener("resize", resizeCanvas); + $('#editVolunteerModal').on('shown.bs.modal', function () { resizeCanvas(); }); @@ -30,13 +31,12 @@ $('#clear-btn').on('click', function () { }); -$('#save-btn').on('click', function () { - var dataURL = signaturePad.toDataURL(); - console.log(dataURL); // You can send this base64 string to your server +$('#save-btn').on('click', function () { $.ajax({ url: `/handbookSignature`, type: "POST", - data: {studentID: $('#handbook-signature-pad').attr('data-student')}, + headers: {'Content-Type': 'application/json'}, + data: JSON.stringify({studentID: $('#handbook-signature-pad').attr('data-student')}), success: function(s){ alert("Thank you for signing the CELTS Handbook. We look forward to your participation in CELTS!"); signaturePad.off(); diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index b1563d773..7841e4ffe 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -716,8 +716,10 @@
Dietary Restrictions

- {% if handbookOverdue and g.current_user.username == volunteer.username%} +
Handbook Signature
+

CELTS Student Handbook

+ {% if handbookOverdue and g.current_user.username == volunteer.username%}
From 549eaf339784db96c626e21833c790ffae2c0d38 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Mon, 13 Jul 2026 16:45:07 -0400 Subject: [PATCH 10/31] Added handbook upload to admin settings page --- app/controllers/admin/userManagement.py | 25 +++++++++++++++++++++- app/controllers/main/routes.py | 5 +++-- app/logic/term.py | 9 ++++---- app/models/term.py | 1 + app/templates/admin/userManagement.html | 28 ++++++++++++++++++++++--- app/templates/main/userProfile.html | 7 +++++-- 6 files changed, 63 insertions(+), 12 deletions(-) diff --git a/app/controllers/admin/userManagement.py b/app/controllers/admin/userManagement.py index ca944b7af..477cb406a 100644 --- a/app/controllers/admin/userManagement.py +++ b/app/controllers/admin/userManagement.py @@ -1,7 +1,13 @@ -from flask import render_template,request, flash, g, abort, redirect, url_for, jsonify +import os +from pathlib import Path + +from app.models.term import Term +from flask import render_template,request, flash, g, abort, redirect, url_for, jsonify, session from playhouse.shortcuts import model_to_dict from peewee import fn, JOIN, DoesNotExist import re +from werkzeug.utils import secure_filename + from app.controllers.admin import admin_bp from app.models.user import User @@ -139,3 +145,20 @@ def addNewTerm(): addNextTerm() flash("New term added", "success") return "" + +@admin_bp.route('/uploadHandbook/', methods = ['POST']) +def uploadHandbook(currentTerm): + term = Term.get_by_id(currentTerm) + dir_path = Path("app/static/files/handbooks") + dir_path.mkdir(parents=True, exist_ok=True) + file = request.files['handbookUploader'] + filename = secure_filename(file.filename) + full_path = os.path.join(dir_path, filename) + if os.path.exists(full_path): + os.remove(full_path) + file.save(full_path) + term.handbook = filename + term.save() + g.current_term = term + flash(f"Handbook saved successfully to {term.description}!", "success") + return redirect(request.referrer) diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index 1e4fe689d..f1aa5ad4d 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -236,7 +236,7 @@ def viewUsersProfile(username): managersList = [id[1] for id in managersProgramDict.items()] totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) handbookOverdue = getHandbookStatus(volunteer) - + currentTerm = Term.get(Term.isCurrentTerm) return render_template ("/main/userProfile.html", username=username, programs = programs, @@ -255,7 +255,8 @@ def viewUsersProfile(username): managersList = managersList, participatedInLabor = getCeltsLaborHistory(volunteer), totalSustainedEngagements = totalSustainedEngagements, - handbookOverdue = handbookOverdue + handbookOverdue = handbookOverdue, + currentTerm = currentTerm ) abort(403) diff --git a/app/logic/term.py b/app/logic/term.py index 1e9238c68..80eab24dc 100644 --- a/app/logic/term.py +++ b/app/logic/term.py @@ -52,11 +52,12 @@ def addPastTerm(description): return createdOldTerm def changeCurrentTerm(term): - oldCurrentTerm = Term.get_by_id(g.current_term) - oldCurrentTerm.isCurrentTerm = False - oldCurrentTerm.save() + activeTerms = Term.select().where(Term.isCurrentTerm) + print("@@@@@@@") + print(list(activeTerms)) + nterms = (Term.update(isCurrentTerm = False).where(Term.isCurrentTerm).execute()) newCurrentTerm = Term.get_by_id(term) newCurrentTerm.isCurrentTerm = True newCurrentTerm.save() session["current_term"] = model_to_dict(newCurrentTerm) - createActivityLog(f"Changed Current Term from {oldCurrentTerm.description} to {newCurrentTerm.description}") + createActivityLog(f"Changed Current Term to {newCurrentTerm.description}") diff --git a/app/models/term.py b/app/models/term.py index 34f85261b..6b63514c4 100644 --- a/app/models/term.py +++ b/app/models/term.py @@ -6,6 +6,7 @@ class Term(baseModel): isSummer = BooleanField(default=False) isCurrentTerm = BooleanField(default=False) termOrder = CharField() + handbook = CharField(null=True) _cache = None diff --git a/app/templates/admin/userManagement.html b/app/templates/admin/userManagement.html index cea20a377..f54b64a56 100644 --- a/app/templates/admin/userManagement.html +++ b/app/templates/admin/userManagement.html @@ -127,8 +127,8 @@

Select the Current Term
-
    - {% for term in terms %} +
      + {% for term in terms %}
    +

+ +
+
+
+
+
Upload the CELTS handbook for {{g.current_term.description}}:
+
    +
  • + + {{g.current_term}} +
  • +
  • + +
  • +
+
+ +
+ +
+ diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 7841e4ffe..e3f6c0650 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -717,8 +717,11 @@
Dietary Restrictions

-
Handbook Signature
-

CELTS Student Handbook

+
Handbook Signature
+ {% if currentTerm.handbook %} +

{{currentTerm.description}}: CELTS Student Handbook

+ {% else %}No handbook available for {{currentTerm.description}} yet. + {% endif %} {% if handbookOverdue and g.current_user.username == volunteer.username%}
From 1edb0276688a6bdfee66531bc6a5e62e7029e4d0 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Mon, 13 Jul 2026 17:03:53 -0400 Subject: [PATCH 11/31] migrates handbook to all terms in the same academic year, including when creating new terms --- app/controllers/admin/userManagement.py | 11 ++++++++--- app/logic/term.py | 13 ++++++++----- app/templates/admin/userManagement.html | 7 +++++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/controllers/admin/userManagement.py b/app/controllers/admin/userManagement.py index 477cb406a..13823ce60 100644 --- a/app/controllers/admin/userManagement.py +++ b/app/controllers/admin/userManagement.py @@ -124,12 +124,14 @@ def userManagement(): currentAdmins = list(User.select().where(User.isCeltsAdmin)) currentStudentStaff = list(User.select().where(User.isCeltsStudentStaff)) + currentTerm = Term.get(Term.isCurrentTerm) if g.current_user.isCeltsAdmin or g.current_user.isProgramManager: return render_template('admin/userManagement.html', terms = terms, programs = list(currentPrograms), currentAdmins = currentAdmins, currentStudentStaff = currentStudentStaff, + currentTerm = currentTerm ) abort(403) @@ -148,7 +150,9 @@ def addNewTerm(): @admin_bp.route('/uploadHandbook/', methods = ['POST']) def uploadHandbook(currentTerm): - term = Term.get_by_id(currentTerm) + term = Term.select().where(Term.id == currentTerm).get() + allAYterm = Term.select().where(Term.academicYear == term.academicYear) + print(list(allAYterm)) dir_path = Path("app/static/files/handbooks") dir_path.mkdir(parents=True, exist_ok=True) file = request.files['handbookUploader'] @@ -157,8 +161,9 @@ def uploadHandbook(currentTerm): if os.path.exists(full_path): os.remove(full_path) file.save(full_path) - term.handbook = filename - term.save() + for t in allAYterm: + t.handbook = filename + t.save() g.current_term = term flash(f"Handbook saved successfully to {term.description}!", "success") return redirect(request.referrer) diff --git a/app/logic/term.py b/app/logic/term.py index 80eab24dc..90b54d653 100644 --- a/app/logic/term.py +++ b/app/logic/term.py @@ -15,17 +15,22 @@ def addNextTerm(): newDescription = newSemesterMap[prevSemester] + " " + str(newYear) newAY = prevTerm.academicYear + handbook = None if prevSemester == "Summer": # we only change academic year when the latest term in the table is Summer year1, year2 = prevTerm.academicYear.split("-") newAY = year2 + "-" + str(int(year2)+1) - + else: # if the previous term is fall or spring, make sure we copy the handbook + handbook = prevTerm.handbook semester = newDescription.split()[0] summer= "Summer" in semester newTerm = Term.create(description=newDescription, year=newYear, academicYear=newAY, isSummer= summer, - termOrder=Term.convertDescriptionToTermOrder(newDescription)) + termOrder=Term.convertDescriptionToTermOrder(newDescription), + handbook=handbook + ) + newTerm.save() return newTerm @@ -52,9 +57,7 @@ def addPastTerm(description): return createdOldTerm def changeCurrentTerm(term): - activeTerms = Term.select().where(Term.isCurrentTerm) - print("@@@@@@@") - print(list(activeTerms)) + activeTerms = Term.select().where(Term.isCurrentTerm) nterms = (Term.update(isCurrentTerm = False).where(Term.isCurrentTerm).execute()) newCurrentTerm = Term.get_by_id(term) newCurrentTerm.isCurrentTerm = True diff --git a/app/templates/admin/userManagement.html b/app/templates/admin/userManagement.html index f54b64a56..f052c269e 100644 --- a/app/templates/admin/userManagement.html +++ b/app/templates/admin/userManagement.html @@ -150,7 +150,7 @@

-
Upload the CELTS handbook for {{g.current_term.description}}:
+
Upload the CELTS handbook for AY {{currentTerm.academicYear}}:
  • @@ -158,9 +158,12 @@

    {{g.current_term}}

  • - +
+ {% if currentTerm.handbook %} + AY {{currentTerm.academicYear}} - CELTS Student Handbook + {% endif %}
From 822234e3d3ba5a653471ff9abf258b9344c74d30 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Mon, 13 Jul 2026 17:17:12 -0400 Subject: [PATCH 12/31] improving usability of signature canvas in profile --- app/static/js/handbookSignature.js | 8 +++--- app/templates/main/userProfile.html | 42 ++++++++++++++++------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/static/js/handbookSignature.js b/app/static/js/handbookSignature.js index 04b903c28..f5813ec0b 100644 --- a/app/static/js/handbookSignature.js +++ b/app/static/js/handbookSignature.js @@ -30,18 +30,16 @@ $('#clear-btn').on('click', function () { signaturePad.clear(); }); - $('#save-btn').on('click', function () { $.ajax({ url: `/handbookSignature`, type: "POST", headers: {'Content-Type': 'application/json'}, data: JSON.stringify({studentID: $('#handbook-signature-pad').attr('data-student')}), - success: function(s){ - alert("Thank you for signing the CELTS Handbook. We look forward to your participation in CELTS!"); + success: function(s){ signaturePad.off(); - $('#save-btn').hide(); - $('#clear-btn').hide(); + $("#signatureConfirmation").show(); + $("#signatureButtons").hide(); const today = new Date(); $("#signatureDate").text(today.toLocaleDateString('en-US')); $("#signatureDate").css("color", "black"); diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index e3f6c0650..77f6dc37a 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -717,28 +717,34 @@
Dietary Restrictions

-
Handbook Signature
- {% if currentTerm.handbook %} -

{{currentTerm.description}}: CELTS Student Handbook

- {% else %}No handbook available for {{currentTerm.description}} yet. - {% endif %} - {% if handbookOverdue and g.current_user.username == volunteer.username%} -
- -
-
-
- -
-
- -
-
- {% endif %} +
Handbook {% if handbookOverdue %}Signature {% endif %}
+ {% if not currentTerm.handbook %} + No handbook available for {{currentTerm.description}} yet. + {% else %} +

{{currentTerm.description}}: CELTS Student Handbook

+ {% if handbookOverdue and g.current_user.username == volunteer.username%} +
+ +
+
+
+ +
+
+ +
+
+ + {% endif %}

+ {% endif %} From 9a0ae291830ff598a074b1a6430926cae4db572c Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Mon, 13 Jul 2026 17:27:38 -0400 Subject: [PATCH 13/31] Handles errors more cleanly when signing --- app/controllers/admin/routes.py | 1 - app/controllers/admin/userManagement.py | 1 - app/static/js/handbookSignature.js | 6 ++++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index ad798ee27..c8a772083 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -694,5 +694,4 @@ def handbookSignature(): if signer: signer.lastHandbookSignature = datetime.now().strftime("%Y-%m-%d") signer.save() - return "", 200 \ No newline at end of file diff --git a/app/controllers/admin/userManagement.py b/app/controllers/admin/userManagement.py index 13823ce60..49c40be25 100644 --- a/app/controllers/admin/userManagement.py +++ b/app/controllers/admin/userManagement.py @@ -152,7 +152,6 @@ def addNewTerm(): def uploadHandbook(currentTerm): term = Term.select().where(Term.id == currentTerm).get() allAYterm = Term.select().where(Term.academicYear == term.academicYear) - print(list(allAYterm)) dir_path = Path("app/static/files/handbooks") dir_path.mkdir(parents=True, exist_ok=True) file = request.files['handbookUploader'] diff --git a/app/static/js/handbookSignature.js b/app/static/js/handbookSignature.js index f5813ec0b..64288a8e9 100644 --- a/app/static/js/handbookSignature.js +++ b/app/static/js/handbookSignature.js @@ -47,6 +47,12 @@ $('#save-btn').on('click', function () { }, error: function(error, status){ console.log(error, status) + $("#signatureConfirmation h3").text("Uh oh. Something wrong. Please seek out help from the CELTS staff") + $("#signatureConfirmation h3").replaceWith(function() { + return $('

', { html: $(this).html() }); + }); + $("#signatureConfirmation").show(); + $("#signatureButtons").hide(); } }) }); \ No newline at end of file From a941d5a32d2eeec599c82496062943c9fafa2259 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Tue, 14 Jul 2026 17:37:49 -0400 Subject: [PATCH 14/31] Very minor cleanup --- app/templates/main/userProfile.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 77f6dc37a..12e095b48 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -722,7 +722,7 @@

Handbook {% if handbookOverdue %}Signature {% endif %}< No handbook available for {{currentTerm.description}} yet. {% else %}

{{currentTerm.description}}: CELTS Student Handbook

- {% if handbookOverdue and g.current_user.username == volunteer.username%} + {% if handbookOverdue and g.current_user.username == volunteer.username %}
@@ -734,7 +734,7 @@
Handbook {% if handbookOverdue %}Signature {% endif %}< - - {% if page_title != 'Create All Volunteer Training' %} + {% if showEventTypeOptions %}
-
-
- -
- - -
-
- - -
-
- - -
- {% if eventData['program'].isBonnerScholars %} -
- - -
- {% endif %} -
- -
- -
- -
- - -
-
- {% set hide = "" if eventData.isRsvpRequired else "display: none" %} -
- - +
+
+ +
+ + +
+
+ + +
+
+ + +
+ {% if eventData['program'].isBonnerScholars %} +
+ + +
+ {% endif %} +
+ +
+ +
+ +
+ + +
+
+ {% set hide = "" if eventData.isRsvpRequired else "display: none" %} +
+ + +
+
+ + +
+
+ + +
+
-
- - -
-
- -
-
-
-
-{% endif %} -
- - -
+ {% endif %} +
+ + +
{% if filepaths %}
@@ -332,6 +345,8 @@

{{page_title}}

+ +
From 5be2c9d4b0350eceac304a945e9511c949943eca Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Wed, 15 Jul 2026 08:16:04 -0400 Subject: [PATCH 19/31] Updates to recurring event logic and model update for event --- app/controllers/admin/routes.py | 2 +- app/logic/events.py | 10 ++++------ app/models/event.py | 9 +++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 7fc519b9f..7586d9c9d 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -114,7 +114,6 @@ def createEvent(templateid, programid): if request.method == "POST": savedEvents = None eventData.update(request.form.copy()) - print("\n\n\n\n\n1\n\n\n\n", eventData) eventData = preprocessEventData(eventData) if eventData.get('isSeries'): @@ -468,6 +467,7 @@ def deleteAllEventsInSeriesRoute(eventId): @admin_bp.route('/makeRepeatingEvents', methods=['POST']) def addRepeatingEvents(): + repeatingEvents = getRepeatingEventsData(preprocessEventData(request.form.copy())) repeatingEvents = getRepeatingEventsData(preprocessEventData(request.form.copy())) return json.dumps(repeatingEvents, default=str) diff --git a/app/logic/events.py b/app/logic/events.py index de0722f26..b691fd8db 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -208,6 +208,7 @@ def saveEventToDb(newEventData, renewedEvent = False): "location": newEventData['location'], "isFoodProvided" : newEventData['isFoodProvided'], "isLaborOnly" : newEventData['isLaborOnly'], + "isCeltsTraining": newEventData['isCeltsTraining'], "includesLabor" : newEventData['includesLabor'], "isTraining": newEventData['isTraining'], "isEngagement": newEventData['isEngagement'], @@ -401,7 +402,7 @@ def getUpcomingEventsForUser(user, asOf=datetime.now(), program=None): return eventsList -#TODO This method appears to be very poorly tested, and very complex. +#TODO This method appears to be very poorly tested, and very complex. NEEDS TESTING! def getParticipatedEventsForUser(user): """ Get all the events a user has participated in. @@ -411,9 +412,6 @@ def getParticipatedEventsForUser(user): :return: A list of Event objects """ - eventName = fn.LOWER(Event.name) - checkIfLaborMeeting = eventName.contains("labor meeting") # Flimsy!! Let's use event.isLaborOnly instead? - # Does this handle labor only and/or includes labor events? participatedEvents = (Event.select(Event, Program.programName, Case(None, ( ((Event.includesLabor | Event.name.contains("Labor")) & Event.isService, "Labor & Volunteer"), @@ -422,7 +420,7 @@ def getParticipatedEventsForUser(user): .join(Program, JOIN.LEFT_OUTER).switch() .join(EventParticipant) .where(EventParticipant.user == user, - Event.isAllVolunteerTraining == False, Event.deletionDate == None, ~checkIfLaborMeeting) + Event.isAllVolunteerTraining == False, Event.deletionDate == None, Event.isLaborOnly == False, Event.isCeltsTraining == False) .order_by(Event.startDate, Event.name)) allVolunteer = (Event.select(Event, "", Value("Volunteer").alias("participatedType")) .join(EventParticipant) @@ -521,7 +519,7 @@ def preprocessEventData(eventData): """ ## Process checkboxes print("\n\n\n\n\n2\n\n\n\n", eventData) - eventCheckBoxes = ['isFoodProvided', 'isRsvpRequired', 'isService', 'isTraining', 'isEngagement', 'isRepeating', 'isAllVolunteerTraining', 'includesLabor', 'isLaborOnly'] + eventCheckBoxes = ['isFoodProvided', 'isRsvpRequired', 'isService', 'isTraining', 'isEngagement', 'isRepeating', 'isAllVolunteerTraining', 'includesLabor', 'isLaborOnly', 'isCeltsTraining'] for checkBox in eventCheckBoxes: if checkBox not in eventData: diff --git a/app/models/event.py b/app/models/event.py index 6453156ee..06fba26d1 100644 --- a/app/models/event.py +++ b/app/models/event.py @@ -11,13 +11,14 @@ class Event(baseModel): timeEnd = TimeField() location = CharField() isFoodProvided = BooleanField(default=False) - includesLabor = BooleanField(default=False) # Event has some labor students working in addition to volunteers - isLaborOnly = BooleanField(default=False) # Event is a labor meeting, specifically for labor students only - isTraining = BooleanField(default=False) + includesLabor = BooleanField(default=False) # Event has some labor students working in addition to volunteers + isLaborOnly = BooleanField(default=False) # Event is a labor meeting, specifically for labor students only + isTraining = BooleanField(default=False) # Event is a training for a Program (required by volunteers to earn service hours in that program) + isAllVolunteerTraining = BooleanField(default=False) # Event is an All Volunteers Training (required to earn any service hours) + isCeltsTraining = BooleanField(default=False) # Event is a CELTS labor training (required by all CELTS labor students) isRsvpRequired = BooleanField(default=False) isService = BooleanField(default=False) isEngagement = BooleanField(default=False) - isAllVolunteerTraining = BooleanField(default=False) rsvpLimit = IntegerField(null=True) startDate = DateField() seriesId = IntegerField(null=True) From 23baf8b44e947e8bc4c2059139e65d219458110d Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Wed, 15 Jul 2026 08:17:50 -0400 Subject: [PATCH 20/31] updates to base data to include two new shortcuts --- database/base_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/base_data.py b/database/base_data.py index 6a67ea948..b343d9278 100644 --- a/database/base_data.py +++ b/database/base_data.py @@ -57,7 +57,7 @@ "id": 3, "name": "All CELTS Training", "tag": "all-celts-training", - "templateJSON": '{"name": "All CELTS Training (Labor)","description": "Training for all CELTS Labor Students", "isTraining": true, "isService": false, "isRequired": true, "isLaborOnly": true, "rsvpLimit": ""}', + "templateJSON": '{"name": "All CELTS Training (Labor)","description": "Training for all CELTS Labor Students", "isTraining": true, "isService": false, "isRequired": true, "isLaborOnly": true, "isCeltsTraining": true, "rsvpLimit": ""}', "templateFile": "createEvent.html", "isVisible": True }, From 5cc86dd8c3fa8d5593f9b8694bf649d006ccbd14 Mon Sep 17 00:00:00 2001 From: Scott Heggen Date: Wed, 15 Jul 2026 19:02:54 -0400 Subject: [PATCH 21/31] updating signature usability --- app/controllers/admin/routes.py | 5 +- app/controllers/admin/userManagement.py | 25 ++++++--- app/controllers/main/routes.py | 17 +++---- app/logic/participants.py | 13 ++++- app/logic/term.py | 17 +++++-- app/models/term.py | 4 +- app/models/user.py | 2 + app/static/css/programManagement.css | 11 ++++ app/static/css/userProfile.css | 2 +- app/static/js/handbookSignature.js | 67 ++++++++++++++----------- app/static/js/userManagement.js | 34 +++++++++++-- app/static/js/userProfile.js | 6 ++- app/templates/admin/userManagement.html | 67 +++++++++++++++++-------- app/templates/main/userProfile.html | 43 ++++++++++------ 14 files changed, 215 insertions(+), 98 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index c8a772083..53defea95 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -690,8 +690,9 @@ def handbookSignature(): if not (g.current_user.username == data.get('studentID')): abort(403) signer = User.get(User.username == g.current_user.username) - print("CURRENT USER: ", signer) if signer: signer.lastHandbookSignature = datetime.now().strftime("%Y-%m-%d") + signer.signatureTerm = g.current_term signer.save() - return "", 200 \ No newline at end of file + return "", 200 + abort(403) \ No newline at end of file diff --git a/app/controllers/admin/userManagement.py b/app/controllers/admin/userManagement.py index 49c40be25..c14b5be0e 100644 --- a/app/controllers/admin/userManagement.py +++ b/app/controllers/admin/userManagement.py @@ -139,8 +139,8 @@ def userManagement(): def changeTerm(): termData = request.form term = int(termData["id"]) - changeCurrentTerm(term) - return "" + newTerm = changeCurrentTerm(term) + return model_to_dict(newTerm) @admin_bp.route('/admin/addNewTerm', methods = ['POST']) def addNewTerm(): @@ -148,20 +148,29 @@ def addNewTerm(): flash("New term added", "success") return "" -@admin_bp.route('/uploadHandbook/', methods = ['POST']) -def uploadHandbook(currentTerm): +@admin_bp.route('/upload//', methods = ['POST']) +def upload(fileCategory, currentTerm): term = Term.select().where(Term.id == currentTerm).get() allAYterm = Term.select().where(Term.academicYear == term.academicYear) - dir_path = Path("app/static/files/handbooks") + if not fileCategory in ["laborHandbook", "volunteerHandbook", "backgroundForm"]: + abort(405) + dir_path = Path("app/static/files/", fileCategory) dir_path.mkdir(parents=True, exist_ok=True) - file = request.files['handbookUploader'] - filename = secure_filename(file.filename) + file = request.files[fileCategory] + filename = g.current_term.academicYear + "-" + fileCategory + "." + secure_filename(file.filename).split(".")[-1] full_path = os.path.join(dir_path, filename) if os.path.exists(full_path): os.remove(full_path) file.save(full_path) for t in allAYterm: - t.handbook = filename + if fileCategory == "volunteerHandbook": + t.volunteerHandbook = filename + elif fileCategory == "laborHandbook": + t.laborHandbook = filename + elif fileCategory == "backgroundForm": + t.backgroundForm = filename + else: + abort(405) t.save() g.current_term = term flash(f"Handbook saved successfully to {term.description}!", "success") diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index f1aa5ad4d..1a33a328b 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -198,7 +198,6 @@ def viewUsersProfile(username): allBackgroundHistory = getUserBGCheckHistory(volunteer) backgroundTypes = list(BackgroundCheckType.select()) - eligibilityTable = [] @@ -237,6 +236,8 @@ def viewUsersProfile(username): totalSustainedEngagements = getEngagementTotal(getCommunityEngagementByTerm(volunteer)) handbookOverdue = getHandbookStatus(volunteer) currentTerm = Term.get(Term.isCurrentTerm) + + return render_template ("/main/userProfile.html", username=username, programs = programs, @@ -261,16 +262,10 @@ def viewUsersProfile(username): abort(403) def getHandbookStatus(volunteer): - handbookOverdue = True - if volunteer.lastHandbookSignature: - today = datetime.date.today() - - # Signature must be during the current academic year (July 1 - June 30) - if datetime.date(today.year, 7, 1) < volunteer.lastHandbookSignature < datetime.date(today.year + 1, 6, 30): - handbookOverdue = False - volunteer.lastHandbookSignature = volunteer.lastHandbookSignature.strftime("%m/%d/%Y") - else: - volunteer.lastHandbookSignature = "NOT SIGNED" + handbookOverdue = False + + if not volunteer.signatureTerm or volunteer.signatureTerm.academicYear != g.current_term.academicYear: + handbookOverdue = True return handbookOverdue @main_bp.route('/profile//emergencyContact', methods=['GET', 'POST']) diff --git a/app/logic/participants.py b/app/logic/participants.py index 630ea0aea..fdd0df6cc 100644 --- a/app/logic/participants.py +++ b/app/logic/participants.py @@ -197,4 +197,15 @@ def sortParticipantsByStatus(event): eventVolunteerData = [volunteer for volunteer in eventNonAttendedData if volunteer not in eventWaitlistData] eventNonAttendedData = [] - return eventNonAttendedData, eventWaitlistData, eventVolunteerData, eventParticipants \ No newline at end of file + return eventNonAttendedData, eventWaitlistData, eventVolunteerData, eventParticipants + +def hasGoneToTraining(participant, term): + """ + Taken in a User object, and returns which training (specifically, All volunteers training or All CELTS labor training) they attended for this term. + This is necessary for delivering the correct handbook to the student for signing. + + return: Event object of a) the All Volunteers training they attended, b) the All Celts training they attended, or c) None + """ + + user = User.get_by_id(participant) + training = Event.select().where(Event.term == term, Event.isAllVolunteerTraining | Event.isCeltsTraining) diff --git a/app/logic/term.py b/app/logic/term.py index 90b54d653..32ca8c6cf 100644 --- a/app/logic/term.py +++ b/app/logic/term.py @@ -15,20 +15,27 @@ def addNextTerm(): newDescription = newSemesterMap[prevSemester] + " " + str(newYear) newAY = prevTerm.academicYear - handbook = None + volunteerHandbook = None + laborHandbook = None + backgroundForm = None if prevSemester == "Summer": # we only change academic year when the latest term in the table is Summer year1, year2 = prevTerm.academicYear.split("-") newAY = year2 + "-" + str(int(year2)+1) else: # if the previous term is fall or spring, make sure we copy the handbook - handbook = prevTerm.handbook + volunteerHandbook = prevTerm.volunteerHandbook + laborHandbook = prevTerm.laborHandbook + backgroundForm = prevTerm.backgroundForm + semester = newDescription.split()[0] summer= "Summer" in semester newTerm = Term.create(description=newDescription, year=newYear, academicYear=newAY, isSummer= summer, - termOrder=Term.convertDescriptionToTermOrder(newDescription), - handbook=handbook + termOrder=Term.convertDescriptionToTermOrder(newDescription), + volunteerHandbook=volunteerHandbook, + laborHandbook=laborHandbook, + backgroundForm=backgroundForm ) newTerm.save() @@ -64,3 +71,5 @@ def changeCurrentTerm(term): newCurrentTerm.save() session["current_term"] = model_to_dict(newCurrentTerm) createActivityLog(f"Changed Current Term to {newCurrentTerm.description}") + + return newCurrentTerm diff --git a/app/models/term.py b/app/models/term.py index 6b63514c4..261c37b0c 100644 --- a/app/models/term.py +++ b/app/models/term.py @@ -6,7 +6,9 @@ class Term(baseModel): isSummer = BooleanField(default=False) isCurrentTerm = BooleanField(default=False) termOrder = CharField() - handbook = CharField(null=True) + volunteerHandbook = CharField(null=True) + laborHandbook = CharField(null=True) + backgroundForm = CharField(null=True) _cache = None diff --git a/app/models/user.py b/app/models/user.py index d0c2ae716..77539b6e9 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,4 +1,5 @@ from app.models import * +from app.models.term import Term class User(baseModel): username = CharField(primary_key=True) @@ -20,6 +21,7 @@ class User(baseModel): hasGraduated = BooleanField(default=False) declaredMinor = BooleanField(default=False) lastHandbookSignature = DateField(null=True) + signatureTerm = ForeignKeyField(Term, null = True) # override BaseModel's __init__ so that we can set up an instance attribute for cache def __init__(self,*args, **kwargs): diff --git a/app/static/css/programManagement.css b/app/static/css/programManagement.css index 7a8717b6e..b7d7b9456 100644 --- a/app/static/css/programManagement.css +++ b/app/static/css/programManagement.css @@ -36,3 +36,14 @@ right: 10px; /* Added px to right */ } +form.uploaders { + margin-bottom: 1em; +} + +.uploaderButtons { + margin-top: 24px; +} + +#collapseTwo div.card { + margin-bottom: 40px; +} \ No newline at end of file diff --git a/app/static/css/userProfile.css b/app/static/css/userProfile.css index 0fba5cd56..461deec8f 100644 --- a/app/static/css/userProfile.css +++ b/app/static/css/userProfile.css @@ -73,4 +73,4 @@ div.profile-links a:not(:first-child) { .tooltip-container:hover .tooltip-text { visibility: visible; opacity: 1; -} \ No newline at end of file +} diff --git a/app/static/js/handbookSignature.js b/app/static/js/handbookSignature.js index 64288a8e9..df23af4dd 100644 --- a/app/static/js/handbookSignature.js +++ b/app/static/js/handbookSignature.js @@ -12,47 +12,58 @@ function resizeCanvas() { canvas.style.width = displayWidth + "px"; canvas.style.height = displayHeight + "px"; canvas.getContext("2d").scale(ratio, ratio); - signaturePad.clear(); + // signaturePad.clear(); } window.addEventListener("resize", resizeCanvas); $('#editVolunteerModal').on('shown.bs.modal', function () { - resizeCanvas(); + resizeCanvas(); }); $('#editVolunteerModal').on('hidden.bs.modal', function () { - $("#signatureHeader").remove(); - canvas.remove(); + if (!signaturePad.isEmpty() & hasSavedSignature) { + $("#signatureHeader").remove(); + canvas.remove(); + // signaturePad.clear(); + $("#signatureButtons").hide(); + } }); $('#clear-btn').on('click', function () { signaturePad.clear(); }); +var hasSavedSignature = false; + $('#save-btn').on('click', function () { - $.ajax({ - url: `/handbookSignature`, - type: "POST", - headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({studentID: $('#handbook-signature-pad').attr('data-student')}), - success: function(s){ - signaturePad.off(); - $("#signatureConfirmation").show(); - $("#signatureButtons").hide(); - const today = new Date(); - $("#signatureDate").text(today.toLocaleDateString('en-US')); - $("#signatureDate").css("color", "black"); - $("#handbookSignatureContainer .bi-info-circle-fill").hide(); - }, - error: function(error, status){ - console.log(error, status) - $("#signatureConfirmation h3").text("Uh oh. Something wrong. Please seek out help from the CELTS staff") - $("#signatureConfirmation h3").replaceWith(function() { - return $('

', { html: $(this).html() }); - }); - $("#signatureConfirmation").show(); - $("#signatureButtons").hide(); - } - }) + if (signaturePad.isEmpty()) { + alert("You forgot to sign!"); + } else { + $.ajax({ + url: `/handbookSignature`, + type: "POST", + headers: {'Content-Type': 'application/json'}, + data: JSON.stringify({studentID: $('#handbook-signature-pad').attr('data-student')}), + success: function(s){ + signaturePad.off(); + $("#signatureConfirmation").show(); + $("#signatureButtons").hide(); + const today = new Date(); + $("#signatureText").text("CELTS Handbook signed for AY 2026-2027"); + $("#signatureText").css("color", "black"); + $("#handbookSignatureContainer .bi-info-circle-fill").hide(); + hasSavedSignature = true; + }, + error: function(error, status){ + console.log(error, status) + $("#signatureConfirmation h3").text("Uh oh. Something wrong. Please seek out help from the CELTS staff") + $("#signatureConfirmation h3").replaceWith(function() { + return $('

', { html: $(this).html() }); + }); + $("#signatureConfirmation").show(); + $("#signatureButtons").hide(); + } + }) + } }); \ No newline at end of file diff --git a/app/static/js/userManagement.js b/app/static/js/userManagement.js index 46535c72a..9cd0e15a4 100644 --- a/app/static/js/userManagement.js +++ b/app/static/js/userManagement.js @@ -251,12 +251,38 @@ function submitTerm(){ url: "/admin/changeTerm", type: "POST", data: termInfo, - success: function(s){ - msgFlash("Current term successfully changed to " + selectedTerm.html(), "success") + success: function(response){ + console.log(response) + msgFlash("Current term successfully changed to " + response["description"], "success") + $(".uploaders").each(function(idx) { + // update the form action attribute to point at the right term + $(this).attr("action", $(this).attr("action").split("/").slice(0, -1).join("/") + "/" + termInfo["id"]); + }) + + // Update all other fields in the form uploader section + $("#collapseTwo h5").text("AY " + response["academicYear"] + " files"); + if(response["volunteerHandbook"]) { + $("#volunteerHandbookURL").text("AY " + response["academicYear"] + " - CELTS Student Handbook") + $("#volunteerHandbookURL").removeAttr("hidden"); + } else { + $("#volunteerHandbookURL").attr("hidden", true); + } + if(response["laborHandbook"]) { + $("#laborHandbookURL").text("AY " + response["academicYear"] + " - CELTS Labor Handbook") + $("#laborHandbookURL").removeAttr("hidden"); + } else { + $("#laborHandbookURL").attr("hidden", true); + } + if(response["backgroundForm"]) { + $("#backgroundFormURL").text("AY " + response["academicYear"] + " - Background Check Form") + $("#backgroundFormURL").removeAttr("hidden"); + } else { + $("#backgroundFormURL").attr("hidden", true); + } }, error: function(error, status){ - msgFlash("Current term was not changed. Please try again.", "warning") - console.log(error, status) + msgFlash("Current term was not changed. Please reload the page and try again.", "warning") + // console.log(error, status) } }) } diff --git a/app/static/js/userProfile.js b/app/static/js/userProfile.js index 293b68592..cb2a57e1f 100644 --- a/app/static/js/userProfile.js +++ b/app/static/js/userProfile.js @@ -1,5 +1,7 @@ -$(document).ready(function(){ - +$(document).ready(function(){ + $('#editVolunteerModal').modal({ + backdrop: 'static' + }); $("#checkDietRestriction").on("change", function() { let norestrict = $(this).is(':checked'); if (norestrict) { diff --git a/app/templates/admin/userManagement.html b/app/templates/admin/userManagement.html index f052c269e..26300986a 100644 --- a/app/templates/admin/userManagement.html +++ b/app/templates/admin/userManagement.html @@ -146,27 +146,54 @@

-
-
-
-
-
Upload the CELTS handbook for AY {{currentTerm.academicYear}}:
-
    -
  • - - {{g.current_term}} -
  • -
  • - -
  • -
- {% if currentTerm.handbook %} - AY {{currentTerm.academicYear}} - CELTS Student Handbook - {% endif %} +
+
AY {{g.current_term.academicYear}} files
+
+ +
+
+ +
- -
+
+ +
+ {{g.current_term}} +
+ + +
+ +
+
+
+
+ + +
+
+ +
+ {{g.current_term}} +
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+ {{g.current_term}} +
+
diff --git a/app/templates/main/userProfile.html b/app/templates/main/userProfile.html index 12e095b48..16926b395 100644 --- a/app/templates/main/userProfile.html +++ b/app/templates/main/userProfile.html @@ -7,10 +7,13 @@ + + {% endblock %} {% block styles %} {{super()}} + {% endblock %} @@ -19,18 +22,10 @@

{{volunteer.firstName}} {{volunteer.lastName}}

-
+
{{volunteer.bnumber}}
{{volunteer.email}}
-
CELTS Handbook signed on: {{volunteer.lastHandbookSignature}} - {% if handbookOverdue -%} -
- - See a Program Manager or CELTS staff member to sign the handbook -
- {% endif %} -
{% if volunteer.major -%} @@ -47,6 +42,7 @@

{{volunteer.firstName}} {{volunteer.lastName}}

{%endif%}
+