From b417d7b9bdf758f02f87d6a98bdd122be8667e9f Mon Sep 17 00:00:00 2001 From: bledsoef Date: Mon, 17 Mar 2025 19:03:51 -0400 Subject: [PATCH 001/407] got most the way done with editing and viewing functionality --- app/controllers/minor/routes.py | 36 +++++++++++++++++-- app/logic/minor.py | 19 ++++++++++ app/static/js/cceMinorProposalManagement.js | 15 ++++++++ .../minor/cceMinorProposalManagement.html | 2 +- .../minor/companyOrganizationInformation.html | 8 ++--- app/templates/minor/profile.html | 1 + .../minor/requestOtherEngagement.html | 30 +++++++++------- app/templates/minor/summerExperience.html | 3 +- .../minor/supervisorInformation.html | 6 ++-- 9 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 app/static/js/cceMinorProposalManagement.js diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 35063dd7e..667ede871 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -3,10 +3,11 @@ from app.controllers.minor import minor_bp from app.models.user import User +from app.models.cceMinorProposal import CCEMinorProposal from app.models.term import Term from app.logic.fileHandler import FileHandler from app.logic.utils import selectSurroundingTerms, getFilesFromRequest -from app.logic.minor import createOtherEngagementRequest, setCommunityEngagementForUser, getSummerExperience, getEngagementTotal, createSummerExperience, getProgramEngagementHistory, getCourseInformation, getCommunityEngagementByTerm, getCCEMinorProposals +from app.logic.minor import createOtherEngagementRequest, updateOtherEngagementRequest, setCommunityEngagementForUser, getSummerExperience, getEngagementTotal, createSummerExperience, updateSummerExperience, getProgramEngagementHistory, getCourseInformation, getCommunityEngagementByTerm, getCCEMinorProposals @minor_bp.route('/profile//cceMinor', methods=['GET']) def viewCceMinor(username): @@ -39,10 +40,39 @@ def requestOtherEngagement(username): return redirect(url_for('minor.viewCceMinor', username=username)) return render_template("minor/requestOtherEngagement.html", + editable = True, user = User.get_by_id(username), selectableTerms = selectSurroundingTerms(g.current_term), - allTerms = getSummerExperience(username)) + otherEngagement = None) +@minor_bp.route('/cceMinor/editOtherEngagement/', methods=['GET', 'POST']) +@minor_bp.route('/cceMinor/viewOtherEngagement/', methods=['GET']) +@minor_bp.route('/cceMinor/viewSummerExperience/', methods=['GET']) +@minor_bp.route('/cceMinor/editSummerExperience/', methods=['GET', 'POST']) +def editOrViewProposal(proposalID: int): + proposal = CCEMinorProposal.get_by_id(int(proposalID)) + if not (g.current_user.isAdmin or g.current_user.username == proposal.student): + return abort(403) + + if request.method == "GET" and 'view' in request.path: + return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/requestSummerExperience.html", + editable = False, + user = User.get_by_id(proposal.student), + proposal = proposal) + + if request.method == "POST": + if "OtherEngagement" in request.path: + updateOtherEngagementRequest(proposalID, request.form) + else: + updateSummerExperience(proposalID, request.form) + + return redirect(url_for('minor.viewCceMinor', username=proposal.student)) + + return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/requestSummerExperience.html", + editable = True, + selectableTerms = selectSurroundingTerms(g.current_term, summerOnly=False if 'OtherEngagement' else True), + user = User.get_by_id(proposal.student), + proposal = proposal) @minor_bp.route('/cceMinor//summerExperience', methods=['GET', 'POST']) def requestSummerExperience(username): @@ -60,7 +90,7 @@ def requestSummerExperience(username): summerTerms = selectSurroundingTerms(g.current_term, summerOnly=True) return render_template("minor/summerExperience.html", - summerTerms = summerTerms, + selectableTerms = summerTerms, user = User.get_by_id(username), ) diff --git a/app/logic/minor.py b/app/logic/minor.py index c59808749..8ac88af7a 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -38,6 +38,18 @@ def createSummerExperience(username, formData): print(f"Error saving summer experience: {e}") raise e +def updateSummerExperience(proposalID, formData): + """ + Given the username of the student and the formData which includes all of + the SummerExperience information, create a new SummerExperience object. + """ + try: + contentAreas = ', '.join(formData.getlist('contentArea')) # Combine multiple content areas + CCEMinorProposal.update(contentAreas=contentAreas, **formData).where(CCEMinorProposal.id == proposalID).execute() + except Exception as e: + print(f"Error saving summer experience: {e}") + raise e + def getCCEMinorProposals(username): proposalList = [] @@ -263,6 +275,13 @@ def createOtherEngagementRequest(username, formData): student = user, **formData ) + +def updateOtherEngagementRequest(proposalID, formData): + """ + Update an existing CCEMinorProposal entry based off of the form data + """ + + CCEMinorProposal.update(**formData).where(CCEMinorProposal.id == proposalID).execute() def saveSummerExperience(username, summerExperience, currentUser): """ diff --git a/app/static/js/cceMinorProposalManagement.js b/app/static/js/cceMinorProposalManagement.js new file mode 100644 index 000000000..cbe05a3c9 --- /dev/null +++ b/app/static/js/cceMinorProposalManagement.js @@ -0,0 +1,15 @@ +$(document).ready(function() { + console.log("hola") + +}) +function changeAction(action){ + console.log(action) + let proposalID = action.id; + let proposalType = $(action).data('type') + let proposalAction = action.value; + // decides what to do based on selection + if (proposalAction == "Edit"){ + location = `/cceMinor/edit${proposalType.replace(/\s+/g, '')}/` + proposalID; + } + } +window.changeAction = changeAction; \ No newline at end of file diff --git a/app/templates/minor/cceMinorProposalManagement.html b/app/templates/minor/cceMinorProposalManagement.html index dc9c6e47d..eedd71157 100644 --- a/app/templates/minor/cceMinorProposalManagement.html +++ b/app/templates/minor/cceMinorProposalManagement.html @@ -24,7 +24,7 @@ {{proposal['status']}} - diff --git a/app/templates/minor/companyOrganizationInformation.html b/app/templates/minor/companyOrganizationInformation.html index c46f9efef..698c0187c 100644 --- a/app/templates/minor/companyOrganizationInformation.html +++ b/app/templates/minor/companyOrganizationInformation.html @@ -6,14 +6,14 @@

Company/Organization Information

- +

- +

@@ -21,10 +21,10 @@

Company/Organization Information

- +
- +
\ No newline at end of file diff --git a/app/templates/minor/profile.html b/app/templates/minor/profile.html index b2af14f5d..d3dc24ca1 100644 --- a/app/templates/minor/profile.html +++ b/app/templates/minor/profile.html @@ -4,6 +4,7 @@ {% block scripts %} {{ super() }} + diff --git a/app/templates/minor/requestOtherEngagement.html b/app/templates/minor/requestOtherEngagement.html index 9ca2bb090..0496ab243 100644 --- a/app/templates/minor/requestOtherEngagement.html +++ b/app/templates/minor/requestOtherEngagement.html @@ -17,6 +17,7 @@ {% endblock %} {% block app_content %} + {% set viewing = (not editable) and (otherExperience != None) %}
@@ -27,16 +28,19 @@

Proposal for Other Community Engaged E Please fill out this form to submit a proposal for another community engaged experience that pertains to at least one of the four following key content areas: power and inequality, community and identity, civic literacy, or civic skills.

- +

- + {% for term in selectableTerms %} - + {% endfor %}
@@ -45,7 +49,7 @@

Proposal for Other Community Engaged E
- +
{% include "minor/companyOrganizationInformation.html" %} @@ -56,7 +60,7 @@

Proposal for Other Community Engaged E
- +
@@ -67,28 +71,30 @@

Experience Information

- +

- +

- +


-
- -
+ {% if not viewing %} +
+ +
+ {% endif %}
{% endblock %} \ No newline at end of file diff --git a/app/templates/minor/summerExperience.html b/app/templates/minor/summerExperience.html index 640a9d84b..bec84dade 100644 --- a/app/templates/minor/summerExperience.html +++ b/app/templates/minor/summerExperience.html @@ -17,6 +17,7 @@ {% endblock %} {% block app_content %} + {% set viewing = not {{editable}} and {{otherExperience}} %}
@@ -33,7 +34,7 @@

Proposal for Community-Engaged Summer Experience

diff --git a/app/templates/minor/supervisorInformation.html b/app/templates/minor/supervisorInformation.html index f774ace1d..d0cad7e32 100644 --- a/app/templates/minor/supervisorInformation.html +++ b/app/templates/minor/supervisorInformation.html @@ -6,7 +6,7 @@

Supervisor Information

- +
@@ -15,11 +15,11 @@

Supervisor Information

- +
- +
\ No newline at end of file From 54ec6788c14ab4f7c3325fd51893527931cc82c9 Mon Sep 17 00:00:00 2001 From: bledsoef Date: Wed, 19 Mar 2025 16:53:16 -0400 Subject: [PATCH 002/407] cleaned up formatting and working on file handling --- app/controllers/minor/routes.py | 3 + app/models/cceMinorProposal.py | 2 - .../minor/companyOrganizationInformation.html | 16 +++- .../minor/requestOtherEngagement.html | 32 +++++-- app/templates/minor/summerExperience.html | 83 +++++++++++++------ .../minor/supervisorInformation.html | 12 ++- 6 files changed, 107 insertions(+), 41 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 667ede871..e8c05d0fc 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -57,6 +57,7 @@ def editOrViewProposal(proposalID: int): if request.method == "GET" and 'view' in request.path: return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/requestSummerExperience.html", editable = False, + contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [], user = User.get_by_id(proposal.student), proposal = proposal) @@ -70,6 +71,7 @@ def editOrViewProposal(proposalID: int): return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/requestSummerExperience.html", editable = True, + contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [], selectableTerms = selectSurroundingTerms(g.current_term, summerOnly=False if 'OtherEngagement' else True), user = User.get_by_id(proposal.student), proposal = proposal) @@ -91,6 +93,7 @@ def requestSummerExperience(username): return render_template("minor/summerExperience.html", selectableTerms = summerTerms, + contentAreas = [], user = User.get_by_id(username), ) diff --git a/app/models/cceMinorProposal.py b/app/models/cceMinorProposal.py index 4e2f0877f..d523d7736 100644 --- a/app/models/cceMinorProposal.py +++ b/app/models/cceMinorProposal.py @@ -21,8 +21,6 @@ class CCEMinorProposal(baseModel): supervisorEmail = CharField() totalHours = IntegerField(null=True) totalWeeks = IntegerField(null=True) - description = TextField() - filename = CharField(null=True) createdOn = DateTimeField(default=datetime.datetime.now) createdBy = ForeignKeyField(User) status = CharField(constraints=[Check("status in ('Approved', 'Pending', 'Denied')")]) diff --git a/app/templates/minor/companyOrganizationInformation.html b/app/templates/minor/companyOrganizationInformation.html index 698c0187c..29f4d98f8 100644 --- a/app/templates/minor/companyOrganizationInformation.html +++ b/app/templates/minor/companyOrganizationInformation.html @@ -6,14 +6,18 @@

Company/Organization Information

- +

- +

@@ -21,10 +25,14 @@

Company/Organization Information

- +
- +
\ No newline at end of file diff --git a/app/templates/minor/requestOtherEngagement.html b/app/templates/minor/requestOtherEngagement.html index 0496ab243..637eafd48 100644 --- a/app/templates/minor/requestOtherEngagement.html +++ b/app/templates/minor/requestOtherEngagement.html @@ -38,9 +38,15 @@

Proposal for Other Community Engaged E {% if viewing %} disabled {% endif %}> - + {% for term in selectableTerms %} - + {% endfor %}

@@ -49,7 +55,9 @@

Proposal for Other Community Engaged E
- +
{% include "minor/companyOrganizationInformation.html" %} @@ -60,7 +68,8 @@

Proposal for Other Community Engaged E
- +
@@ -71,28 +80,35 @@

Experience Information

- +

- +

- +


{% if not viewing %}
- +
{% endif %}
diff --git a/app/templates/minor/summerExperience.html b/app/templates/minor/summerExperience.html index bec84dade..f70674a34 100644 --- a/app/templates/minor/summerExperience.html +++ b/app/templates/minor/summerExperience.html @@ -17,7 +17,7 @@ {% endblock %} {% block app_content %} - {% set viewing = not {{editable}} and {{otherExperience}} %} + {% set viewing = not editable and otherExperience %}
@@ -32,10 +32,19 @@

Proposal for Community-Engaged Summer Experience

- + {% for term in selectableTerms %} - + {% endfor %}
@@ -56,61 +65,87 @@

Experience Information

-
+ +

- +
- +
- +
- +
- +
- +
-
+ + +

- +
- +
- +
- +


- +
- +

- + +

- + +


- -
- -
+ {% if not viewing %} +
+ +
+ {% endif %}

diff --git a/app/templates/minor/supervisorInformation.html b/app/templates/minor/supervisorInformation.html index d0cad7e32..01a69e5fd 100644 --- a/app/templates/minor/supervisorInformation.html +++ b/app/templates/minor/supervisorInformation.html @@ -6,7 +6,9 @@

Supervisor Information

- +
@@ -15,11 +17,15 @@

Supervisor Information

- +
- +
\ No newline at end of file From c54d7d0b5be1bffe6651555cd28638b82cd23e75 Mon Sep 17 00:00:00 2001 From: bledsoef Date: Thu, 20 Mar 2025 17:06:04 -0400 Subject: [PATCH 003/407] almost done with PR --- app/controllers/minor/routes.py | 6 ++- app/logic/minor.py | 2 + app/static/js/cceMinorProposalManagement.js | 5 ++- app/static/js/minorProfilePage.js | 8 +++- .../minor/requestOtherEngagement.html | 20 +++++---- app/templates/minor/summerExperience.html | 44 +++++++++++++------ 6 files changed, 58 insertions(+), 27 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index e8c05d0fc..ba6073740 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -55,10 +55,12 @@ def editOrViewProposal(proposalID: int): return abort(403) if request.method == "GET" and 'view' in request.path: - return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/requestSummerExperience.html", + selectedTerm = Term.get_by_id(proposal.term) + return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/summerExperience.html", editable = False, contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [], user = User.get_by_id(proposal.student), + selectedTerm = selectedTerm, proposal = proposal) if request.method == "POST": @@ -69,7 +71,7 @@ def editOrViewProposal(proposalID: int): return redirect(url_for('minor.viewCceMinor', username=proposal.student)) - return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/requestSummerExperience.html", + return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/summerExperience.html", editable = True, contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [], selectableTerms = selectSurroundingTerms(g.current_term, summerOnly=False if 'OtherEngagement' else True), diff --git a/app/logic/minor.py b/app/logic/minor.py index 8ac88af7a..f86a72e8f 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -24,6 +24,8 @@ def createSummerExperience(username, formData): the SummerExperience information, create a new SummerExperience object. """ try: + # if the Total Hours and Total Weeks fields are disabled they will default to 0 which will actually mean over 300 + # (don't blame us blame CELTS) user = User.get(User.username == username) contentAreas = ', '.join(formData.getlist('contentArea')) # Combine multiple content areas CCEMinorProposal.create( diff --git a/app/static/js/cceMinorProposalManagement.js b/app/static/js/cceMinorProposalManagement.js index cbe05a3c9..5cfa22b69 100644 --- a/app/static/js/cceMinorProposalManagement.js +++ b/app/static/js/cceMinorProposalManagement.js @@ -1,9 +1,7 @@ $(document).ready(function() { - console.log("hola") }) function changeAction(action){ - console.log(action) let proposalID = action.id; let proposalType = $(action).data('type') let proposalAction = action.value; @@ -11,5 +9,8 @@ function changeAction(action){ if (proposalAction == "Edit"){ location = `/cceMinor/edit${proposalType.replace(/\s+/g, '')}/` + proposalID; } + if (proposalAction == "View"){ + location = `/cceMinor/view${proposalType.replace(/\s+/g, '')}/` + proposalID; + } } window.changeAction = changeAction; \ No newline at end of file diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index defa20037..87105914c 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -16,6 +16,10 @@ $(document).ready(function() { } }) + $('#exitButton').on('click', function() { + let username = $("#username").val() + window.location.href = `/profile/${username}/cceMinor` + }) // ************** SUSTAINED COMMUNITY ENGAGEMENTS ************** // $('.engagement-row').on("click", function() { showEngagementInformation($(this).data('engagement-data')); @@ -199,8 +203,10 @@ function toggleUnder300HoursTextarea() { var conditionalTextBox = $('#hoursBelow300Container'); if (yesRadio.is(':checked')) { conditionalTextBox.hide() + $('#totalHours').val(300) } else { - conditionalTextBox.show() + conditionalTextBox.show() + $('#totalHours').val('') } } diff --git a/app/templates/minor/requestOtherEngagement.html b/app/templates/minor/requestOtherEngagement.html index 637eafd48..a88950028 100644 --- a/app/templates/minor/requestOtherEngagement.html +++ b/app/templates/minor/requestOtherEngagement.html @@ -42,6 +42,11 @@

Proposal for Other Community Engaged E {% if not proposal %} selected {% endif %}> Select Year + {% if viewing %} + + {% endif %} {% for term in selectableTerms %}

id='repeatingEventsNamePicker' /> +
+ +
+ +
+
From 4c5559b4aec295e391b3b4c9930b53a5458ee3ff Mon Sep 17 00:00:00 2001 From: Besher Kitaz Date: Sat, 28 Jun 2025 12:43:59 -0400 Subject: [PATCH 035/407] Location field moved to the modal for weekly events --- app/logic/events.py | 8 ++++---- app/static/js/createEvents.js | 18 ++++++++++++------ app/templates/events/createEvent.html | 5 +++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/logic/events.py b/app/logic/events.py index f7e63cfac..b1f5a077e 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -130,7 +130,8 @@ def attemptSaveMultipleOfferings(eventData, attachmentFiles = None): 'timeStart': event['startTime'], 'timeEnd': event['endTime'], 'seriesId': seriesId, - 'isRepeating': bool(isRepeating) + 'isRepeating': bool(isRepeating), + 'location': event['eventLocation'], }) # Try to save each offering savedEvents, validationErrorMessage = attemptSaveEvent(eventInfo, attachmentFiles) @@ -453,9 +454,9 @@ def getRepeatingEventsData(eventData): Return a list of events to create from the event data. """ - return [ {'name': f"{eventData['name']} Week {counter+1}", 'date': eventData['startDate'] + timedelta(days=7*counter), + 'location': eventData['location'], "week": counter+1} for counter in range(0, ((eventData['endDate']-eventData['startDate']).days//7)+1)] @@ -499,7 +500,7 @@ def preprocessEventData(eventData): eventData['term'] = Term.get_by_id(eventData['term']) except DoesNotExist: eventData['term'] = '' - + # Process requirement if 'certRequirement' in eventData: try: @@ -516,7 +517,6 @@ def preprocessEventData(eventData): if 'timeEnd' in eventData: eventData['timeEnd'] = format24HourTime(eventData['timeEnd']) - return eventData def getTomorrowsEvents(): diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index ccd5f6ee1..1987b7257 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -37,7 +37,10 @@ function calculateRepeatingEventFrequency(){ var eventDatesAndName = {name:$("#repeatingEventsNamePicker").val(), isRepeating: true, startDate:$("#repeatingEventsStartDate").val(), - endDate:$("#repeatingEventsEndDate").val()} + endDate:$("#repeatingEventsEndDate").val(), + location:$("#repeatingEventsLocationPicker").val() + } + console.log(eventDatesAndName); $.ajax({ type:"POST", url: "/makeRepeatingEvents", @@ -157,6 +160,7 @@ $('#saveSeries').on('click', function() { //Requires that modal info updated before it can be saved, gives notifier if there are empty fields let eventOfferings = $('#multipleOfferingSlots .eventOffering'); let eventNameInputs = $('#multipleOfferingSlots .multipleOfferingNameField'); + let eventLocationInput = $('') let datePickerInputs = $('#multipleOfferingSlots .multipleOfferingDatePicker'); let startTimeInputs = $('#multipleOfferingSlots .multipleOfferingStartTime'); let endTimeInputs = $('#multipleOfferingSlots .multipleOfferingEndTime'); @@ -188,7 +192,6 @@ $('#saveSeries').on('click', function() { } }); - // Check if the start time is after the end time for(let i = 0; i < startTimeInputs.length; i++){ let startTime = startTimeInputs[i].value @@ -220,7 +223,7 @@ $('#saveSeries').on('click', function() { let eventName = eventNameInputs[i].value let date = datePickerInputs[i].value.trim() let startTime = startTimeInputs[i].value - let eventListing = JSON.stringify([eventName, date, startTime]) + let eventListing = JSON.stringify([eventName, date, startTime, location]) if (eventListing in eventListings){ // If we've seen this event before mark this event and the previous as duplicates hasDuplicateListings = true @@ -289,7 +292,7 @@ function saveOfferingsFromModal() { else { rowData = $.map($(element).find("input"), (el) => $(el).val()); } - console.log(rowData); + let startTime = isRepeatingStatus ? $("#repeatingEventsStartTime").val() : rowData[3] let endTime = isRepeatingStatus ? $("#repeatingEventsEndTime").val() : rowData[4] if (navigator.userAgent.indexOf("Chrome") == -1) { @@ -298,8 +301,8 @@ function saveOfferingsFromModal() { } offerings.push({ eventName: rowData[0], - eventLocation: rowData[1], - eventDate: rowData[2], + eventLocation: isRepeatingStatus ? rowData[2] : rowData[1], + eventDate: isRepeatingStatus ? rowData[1] : rowData[2], startTime: startTime, endTime: endTime, }) @@ -345,10 +348,13 @@ function loadOfferingsToModal(){ function loadRepeatingOfferingToModal(offering){ var seriesTable = $("#generatedEventsTable"); var eventDate = new Date(offering.date || offering.eventDate).toLocaleDateString(); + console.log(offering); + seriesTable.append( "" + "" + (offering.name || offering.eventName) + "" + "" + eventDate + "" + + "" + offering.location + "" + "
" + "" ); diff --git a/app/templates/events/createEvent.html b/app/templates/events/createEvent.html index 915c59eaa..9300b65c3 100644 --- a/app/templates/events/createEvent.html +++ b/app/templates/events/createEvent.html @@ -464,12 +464,12 @@
- +
+ id='repeatingEventsLocationPicker' />
@@ -536,6 +536,7 @@

Generated Events:

Event Name Date + Location Action From 8610ab6c15e22513f9c0ee56fe216998bf73a8ca Mon Sep 17 00:00:00 2001 From: chapagainp Date: Mon, 30 Jun 2025 11:29:21 -0400 Subject: [PATCH 036/407] Added the eventLocation --- app/logic/events.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/logic/events.py b/app/logic/events.py index b1f5a077e..94b7f8ab2 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -130,8 +130,8 @@ def attemptSaveMultipleOfferings(eventData, attachmentFiles = None): 'timeStart': event['startTime'], 'timeEnd': event['endTime'], 'seriesId': seriesId, - 'isRepeating': bool(isRepeating), - 'location': event['eventLocation'], + 'isRepeating': bool(isRepeating), + 'location': event['eventLocation'] }) # Try to save each offering savedEvents, validationErrorMessage = attemptSaveEvent(eventInfo, attachmentFiles) @@ -454,10 +454,12 @@ def getRepeatingEventsData(eventData): Return a list of events to create from the event data. """ + return [ {'name': f"{eventData['name']} Week {counter+1}", 'date': eventData['startDate'] + timedelta(days=7*counter), - 'location': eventData['location'], - "week": counter+1} + "week": counter+1, + 'location': eventData['location'] + } for counter in range(0, ((eventData['endDate']-eventData['startDate']).days//7)+1)] def preprocessEventData(eventData): @@ -500,7 +502,7 @@ def preprocessEventData(eventData): eventData['term'] = Term.get_by_id(eventData['term']) except DoesNotExist: eventData['term'] = '' - + # Process requirement if 'certRequirement' in eventData: try: @@ -517,6 +519,7 @@ def preprocessEventData(eventData): if 'timeEnd' in eventData: eventData['timeEnd'] = format24HourTime(eventData['timeEnd']) + return eventData def getTomorrowsEvents(): From 15fddfc85f0f2c2de1113b42316709e85da573e8 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Tue, 1 Jul 2025 11:28:13 -0400 Subject: [PATCH 037/407] fixed the location error --- app/logic/events.py | 2 +- tests/code/test_events.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/logic/events.py b/app/logic/events.py index 94b7f8ab2..0f40f9da8 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -129,9 +129,9 @@ def attemptSaveMultipleOfferings(eventData, attachmentFiles = None): 'startDate': event['eventDate'], 'timeStart': event['startTime'], 'timeEnd': event['endTime'], + 'location': eventData['location'], 'seriesId': seriesId, 'isRepeating': bool(isRepeating), - 'location': event['eventLocation'] }) # Try to save each offering savedEvents, validationErrorMessage = attemptSaveEvent(eventInfo, attachmentFiles) diff --git a/tests/code/test_events.py b/tests/code/test_events.py index c7a8ebdda..4065c7017 100644 --- a/tests/code/test_events.py +++ b/tests/code/test_events.py @@ -299,13 +299,15 @@ def test_calculateRecurringEventFrequency(): eventInfo = {'name': "testEvent", 'startDate': parser.parse("02/22/2023"), - 'endDate': parser.parse("03/11/2023")} + 'endDate': parser.parse("03/11/2023"), + 'location':("a big room")} # test correct response returnedEvents = getRepeatingEventsData(eventInfo) - assert returnedEvents[0] == {'name': 'testEvent Week 1', 'date': parser.parse('02/22/2023'), 'week': 1} - assert returnedEvents[1] == {'name': 'testEvent Week 2', 'date': parser.parse('03/01/2023'), 'week': 2} - assert returnedEvents[2] == {'name': 'testEvent Week 3', 'date': parser.parse('03/08/2023'), 'week': 3} + assert returnedEvents[0] == {'name': 'testEvent Week 1', 'date': parser.parse('02/22/2023'), 'week': 1, 'location': 'a big room'} + assert returnedEvents[1] == {'name': 'testEvent Week 2', 'date': parser.parse('03/01/2023'), 'week': 2, 'location': 'a big room'} + assert returnedEvents[2] == {'name': 'testEvent Week 3', 'date': parser.parse('03/08/2023'), 'week': 3, 'location': 'a big room'} + # test non-datetime eventInfo["startDate"] = '2021/06/07' From e01f7d741de2240ad3465a622f0299f75db0601e Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Tue, 1 Jul 2025 15:34:24 -0400 Subject: [PATCH 038/407] added event location --- app/controllers/admin/routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 88ca5f655..baf4a7b2e 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -71,7 +71,7 @@ def switchUser(): @admin_bp.route('/eventTemplates') def templateSelect(): - if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff: + if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff or g.current_user.isStudent: allprograms = getAllowedPrograms(g.current_user) visibleTemplates = getAllowedTemplates(g.current_user) return render_template("/events/templateSelector.html", @@ -84,7 +84,7 @@ def templateSelect(): @admin_bp.route('/eventTemplates///create', methods=['GET','POST']) def createEvent(templateid, programid): - if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)): + if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid) or g.current_user.isStudent): abort(403) # Validate given URL From bbe4ee842a11b38a82d3a9c660c2a66ec2d6e8d7 Mon Sep 17 00:00:00 2001 From: chapagainp Date: Wed, 2 Jul 2025 16:34:16 -0400 Subject: [PATCH 039/407] Fixed the issue for Location in the Modal --- app/static/js/createEvents.js | 96 ++++++++++++++++++--------- app/templates/events/createEvent.html | 2 +- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 1987b7257..7e097dae4 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -113,16 +113,21 @@ function initializeFlatpickr(obj) { }); } -let eventSessionNum = 2 -function createOfferingModalRow({eventName=null, eventDate=null, startTime=null, endTime=null}={}){ +let eventSessionNum = 0; +function createOfferingModalRow({eventName=null, eventDate=null, startTime=null, endTime=null, eventLocation = null}={}){ let clonedOffering = $("#multipleOfferingEvent").clone().removeClass('d-none').removeAttr("id"); + const baseName = $('#inputEventName').val(); + const fullName = baseName + ': session ' + (eventSessionNum + 1); + clonedOffering.find('.multipleOfferingNameField').val(fullName); + // insert values for the newly created row if (eventName) {clonedOffering.find('.multipleOfferingNameField').val(eventName)} if (eventDate) {clonedOffering.find('.multipleOfferingDatePicker').val(eventDate)} if (startTime) {clonedOffering.find('.multipleOfferingStartTime').val(startTime)} if (endTime) {clonedOffering.find('.multipleOfferingEndTime').val(endTime)} - $('#eventName').val($('#inputEventName').val() + ': session ' + eventSessionNum); + if (eventLocation) {clonedOffering.find('.multipleOfferingLocationField').val(eventLocation)} + eventSessionNum++; $("#multipleOfferingSlots").append(clonedOffering); pendingmultipleEvents.push(clonedOffering); @@ -164,6 +169,7 @@ $('#saveSeries').on('click', function() { let datePickerInputs = $('#multipleOfferingSlots .multipleOfferingDatePicker'); let startTimeInputs = $('#multipleOfferingSlots .multipleOfferingStartTime'); let endTimeInputs = $('#multipleOfferingSlots .multipleOfferingEndTime'); + let locationInputs = $('#multipleOfferingSlots .multipleOfferingLocationField'); let isRepeatingStatus = $("#checkIsRepeating").is(":checked"); let dataTable = isRepeatingStatus ? "#generatedEventsList" : "#multipleOfferingSlots"; let isEmpty = false; @@ -190,7 +196,16 @@ $('#saveSeries').on('click', function() { } else { $(datePickerInput).removeClass('border-red'); } - }); + }); + + locationInputs.each((index, locationInput) => { + if (locationInput.value.trim() === '') { + isEmpty = true; + $(locationInput).addClass('border-red'); + } else { + $(locationInput).removeClass('border-red'); + } + }); // Check if the start time is after the end time for(let i = 0; i < startTimeInputs.length; i++){ @@ -233,7 +248,7 @@ $('#saveSeries').on('click', function() { } if (isEmpty){ - let emptyFieldMessage = "Event name or date field is empty"; + let emptyFieldMessage = "Event name, date or location field is empty"; displayNotification(emptyFieldMessage); } else if (!hasValidTimes) { @@ -334,15 +349,19 @@ function loadOfferingsToModal(){ let offerings = JSON.parse($("#seriesData").val()) if (offerings.length < 1) {return;} let isRepeatingStatus = $("#checkIsRepeating").is(":checked"); + let mainLocation = $('#inputEventLocation-main').val(); if (isRepeatingStatus) {$("#generatedEvents").removeClass("d-none"); $("#generatedEventsTable tbody tr").remove();}; offerings.forEach((offering, i) =>{ if (isRepeatingStatus){ loadRepeatingOfferingToModal(offering); } else { - let newOfferingModalRow = createOfferingModalRow(offering); + let newOfferingModalRow = createOfferingModalRow({ + ...offering, + eventLocation: offering.eventLocation || mainLocation + }); //stripes odd event sections in event modal newOfferingModalRow.css('background-color', i % 2 ?'#f2f2f2':'#fff'); - }}) + }}); } function loadRepeatingOfferingToModal(offering){ @@ -472,38 +491,45 @@ $(document).ready(function() { } - let modalOpenedByEditButton = false; - //#checkIsRepeating, #checkIsSeries are attributes for the toggle buttons on create event page] - +let modalOpenedByEditButton = false; +// #checkIsRepeating, #checkIsSeries are attributes for the toggle buttons on create event page - $("#checkIsSeries, #edit_modal").click(function(event) { - eventSessionNum = 2 - $('#eventName').val($('#inputEventName').val() + ': session 1') +$("#checkIsSeries, #edit_modal").click(function(event) { + eventSessionNum = 0; + // Set all modal location fields to the main location value (if needed) + $('.multipleOfferingLocationField').val($('#inputEventLocation-main').val()); - let isSeries = $("#checkIsSeries").is(":checked") - modalOpenedByEditButton = ($(this).attr('id') === 'edit_modal'); + let isSeries = $("#checkIsSeries").is(":checked") + modalOpenedByEditButton = ($(this).attr('id') === 'edit_modal'); - if (isSeries) { - if(($('#inputEventName').val().trim() == '')){ - //keeps main page event name for multiple event modal + if (isSeries) { + if ($('#inputEventName').val().trim() == '') { $('#checkIsSeries').prop('checked', false) msgFlash("Please type the event name first") return } - setViewForSeries(); - loadOfferingsToModal(); - $('#modalSeries').modal('show'); - - // Disable single event name field - $('#inputEventName').prop('readonly', true) - } else { - setViewForSingleOffering() - $('#multipleOfferingTableDiv').addClass('d-none'); - // Enable single event name field - $('#inputEventName').prop('readonly', false) - $('#inputEventName').prop('placeholder', 'Enter event name') + if ($('#inputEventLocation-main').val().trim() == '') { + $('#checkIsSeries').prop('checked', false) + msgFlash("Please type the event location first") + return } - }); + setViewForSeries(); + loadOfferingsToModal(); + $('#modalSeries').modal('show'); + + // Disable single event name and location fields + $('#inputEventName').prop('readonly', true) + $('#inputEventLocation-main').prop('readonly', true) + } else { + setViewForSingleOffering() + $('#multipleOfferingTableDiv').addClass('d-none'); + // Enable single event name and location fields + $('#inputEventName').prop('readonly', false) + $('#inputEventName').prop('placeholder', 'Enter event name') + $('#inputEventLocation-main').prop('readonly', false) + $('#inputEventLocation-main').prop('placeholder', 'Enter event location') + } +}); //untoggles the button when the modal cancel or close button is clicked $("#cancelModalPreview, #multipleOfferingXbutton").click(function(){ @@ -521,6 +547,8 @@ $(document).ready(function() { $('#inputEventName').prop('readonly', false) $('#inputEventName').prop('placeholder', 'Enter event name') checkIfDateInPast(); + $('#inputEventLocation-main').prop('readonly', false) + $('#inputEventLocation-main').prop('placeholder', 'Enter event location') } }); @@ -581,7 +609,11 @@ $(document).ready(function() { /*cloning the div with ID multipleOfferingEvent and cloning, changing the ID of each clone going up by 1. This also changes the ID of the deleteMultipleOffering so that when the trash icon is clicked, that specific row will be deleted*/ - $(".addMultipleOfferingEvent").click(createOfferingModalRow) + $(".addMultipleOfferingEvent").click(function() { + // Get the current value from the main location input + let mainLocation = $("#inputEventLocation-main").val(); + createOfferingModalRow({eventLocation: mainLocation}); +}); var minDate = new Date('10/25/1999') $("#startDatePicker-main").datepicker("option", "minDate", minDate) diff --git a/app/templates/events/createEvent.html b/app/templates/events/createEvent.html index 9300b65c3..7ad701203 100644 --- a/app/templates/events/createEvent.html +++ b/app/templates/events/createEvent.html @@ -554,7 +554,7 @@

Generated Events:

-
From 101a9df2bde8f1e7f869a170583592ad8eb312a9 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Thu, 3 Jul 2025 11:59:34 -0400 Subject: [PATCH 040/407] We fixed the location field in multiple offering so that it shows up in the table and not as undefined. --- app/static/js/createEvents.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 7e097dae4..ce1532451 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -38,7 +38,7 @@ function calculateRepeatingEventFrequency(){ isRepeating: true, startDate:$("#repeatingEventsStartDate").val(), endDate:$("#repeatingEventsEndDate").val(), - location:$("#repeatingEventsLocationPicker").val() + location:$("#repeatingEventsLocationPicker").val() || '' } console.log(eventDatesAndName); $.ajax({ @@ -373,7 +373,7 @@ function loadRepeatingOfferingToModal(offering){ "" + "" + (offering.name || offering.eventName) + "" + "" + eventDate + "" + - "" + offering.location + "" + + "" + (offering.eventLocation || offering.location)+ "" + "
" + "" ); @@ -394,7 +394,7 @@ function updateOfferingsTable() { "" + formattedEventDate + "" + "" + startTime + "" + "" + endTime + "" + - "" + offering.eventLocation + "" + + "" + (offering.eventLocation || offering.location || "") + "" + "" ); }); @@ -574,6 +574,7 @@ $("#checkIsSeries, #edit_modal").click(function(event) { let endDate = new Date($("#repeatingEventsEndDate").val()); let startTime = $("#repeatingEventsStartTime").val(); let endTime = $("#repeatingEventsEndTime").val(); + let eventLocation = $("#repeatingEventsLocationPicker").val() || ''; if (navigator.userAgent.indexOf("Chrome") == -1) { startTime = format12to24HourTime(startTime) From 986c38b9e6641431344bf1a29fb8b1221c7fc04d Mon Sep 17 00:00:00 2001 From: chapagainp Date: Thu, 3 Jul 2025 16:24:47 -0400 Subject: [PATCH 041/407] Removed some non-essential code, used .hide() and .show() instead of removeClass() and addClass(), and changed the message for flash --- app/static/js/createEvents.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index ce1532451..cd1f57b6c 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -63,20 +63,20 @@ function calculateRepeatingEventFrequency(){ function setViewForSingleOffering(){ $(".startDatePicker").prop('required', true); - $("#multipleOfferingTableDiv").addClass('d-none'); - $("#eventLocation-main").removeClass('d-none'); + $("#multipleOfferingTableDiv").hide(); + $("#eventLocation-main").show(); $("#inputEventLocation-main").prop('required', true); - $('#eventTime, #eventDate').removeClass('d-none'); + $('#eventTime, #eventDate').show(); $('#checkIsSeriesToggleContainer').addClass('col-md-6') $('#checkIsSeriesToggleContainer').removeClass('col-md-12') } function setViewForSeries(){ $(".startDatePicker").prop('required', false); - $("#multipleOfferingTableDiv").removeClass('d-none'); - $("#eventLocation-main").addClass('d-none'); + $("#multipleOfferingTableDiv").show(); + $("#eventLocation-main").hide(); $("#inputEventLocation-main").prop('required', false); - $('#eventTime, #eventDate').addClass('d-none'); + $('#eventTime, #eventDate').hide(); $('#checkIsSeriesToggleContainer').removeClass('col-md-6') $('#checkIsSeriesToggleContainer').addClass('col-md-12') $("#pastDateWarningText").text("") @@ -256,7 +256,7 @@ $('#saveSeries').on('click', function() { displayNotification(invalidTimeMessage); } else if (hasDuplicateListings) { - let eventConflictMessage = "Event listings cannot have the same event name, date, and start time"; + let eventConflictMessage = "Event listings cannot have the same event name, date, location and start time"; displayNotification(eventConflictMessage); } else { saveOfferingsFromModal(); From 3abb92ade0192536d3acca05ddc088f650994836 Mon Sep 17 00:00:00 2001 From: chapagainp Date: Tue, 8 Jul 2025 11:04:30 -0400 Subject: [PATCH 042/407] Fixed the table issue --- app/static/js/createEvents.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index cd1f57b6c..ea58703a9 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -63,20 +63,21 @@ function calculateRepeatingEventFrequency(){ function setViewForSingleOffering(){ $(".startDatePicker").prop('required', true); - $("#multipleOfferingTableDiv").hide(); - $("#eventLocation-main").show(); + $("#multipleOfferingTableDiv").addClass('d-none'); + $("#eventLocation-main").addClass('d-none'); + $("#eventLocation-main").removeClass('d-none'); $("#inputEventLocation-main").prop('required', true); - $('#eventTime, #eventDate').show(); + $('#eventTime, #eventDate').removeClass('d-none'); $('#checkIsSeriesToggleContainer').addClass('col-md-6') $('#checkIsSeriesToggleContainer').removeClass('col-md-12') } function setViewForSeries(){ $(".startDatePicker").prop('required', false); - $("#multipleOfferingTableDiv").show(); - $("#eventLocation-main").hide(); + $("#multipleOfferingTableDiv").removeClass('d-none'); + $("#eventLocation-main").removeClass('d-none'); $("#inputEventLocation-main").prop('required', false); - $('#eventTime, #eventDate').hide(); + $('#eventTime, #eventDate').addClass('d-none'); $('#checkIsSeriesToggleContainer').removeClass('col-md-6') $('#checkIsSeriesToggleContainer').addClass('col-md-12') $("#pastDateWarningText").text("") From 21be475e6e4008e93bd5cbb613318f5ad9062ef5 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Tue, 8 Jul 2025 15:08:26 -0400 Subject: [PATCH 043/407] Removed the flash messages from the main event page based on Karina's suggestion. Removed the ability for student user to create an event. --- app/controllers/admin/routes.py | 2 +- app/static/js/createEvents.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 8d5b75aa9..542704985 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -84,7 +84,7 @@ def templateSelect(): @admin_bp.route('/eventTemplates///create', methods=['GET','POST']) def createEvent(templateid, programid): - if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid) or g.current_user.isStudent): + if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)): abort(403) # Validate given URL diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index ea58703a9..a61052e0e 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -506,12 +506,10 @@ $("#checkIsSeries, #edit_modal").click(function(event) { if (isSeries) { if ($('#inputEventName').val().trim() == '') { $('#checkIsSeries').prop('checked', false) - msgFlash("Please type the event name first") return } if ($('#inputEventLocation-main').val().trim() == '') { $('#checkIsSeries').prop('checked', false) - msgFlash("Please type the event location first") return } setViewForSeries(); From 2ccb1f1414338a0990d20efdc6b4ea67fa19b4d8 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Tue, 8 Jul 2025 15:14:46 -0400 Subject: [PATCH 044/407] Replace flash messages with input focus on validation failure --- app/static/js/createEvents.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index a61052e0e..9dfffbe31 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -506,10 +506,12 @@ $("#checkIsSeries, #edit_modal").click(function(event) { if (isSeries) { if ($('#inputEventName').val().trim() == '') { $('#checkIsSeries').prop('checked', false) + $('#inputEventName').focus(); return } if ($('#inputEventLocation-main').val().trim() == '') { $('#checkIsSeries').prop('checked', false) + $('#inputEventLocation-main').focus(); return } setViewForSeries(); From 2a759bc34b864731072755f0231139a067ba8f88 Mon Sep 17 00:00:00 2001 From: chapagainp Date: Wed, 9 Jul 2025 10:56:00 -0400 Subject: [PATCH 045/407] Using a combination of hide/show and addClass/removeClass --- app/static/js/createEvents.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 9dfffbe31..8c6cf3405 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -64,20 +64,20 @@ function calculateRepeatingEventFrequency(){ function setViewForSingleOffering(){ $(".startDatePicker").prop('required', true); $("#multipleOfferingTableDiv").addClass('d-none'); - $("#eventLocation-main").addClass('d-none'); - $("#eventLocation-main").removeClass('d-none'); + $("#eventLocation-main").hide(); + $("#eventLocation-main").show(); $("#inputEventLocation-main").prop('required', true); - $('#eventTime, #eventDate').removeClass('d-none'); - $('#checkIsSeriesToggleContainer').addClass('col-md-6') - $('#checkIsSeriesToggleContainer').removeClass('col-md-12') + $('#eventTime, #eventDate').show(); + $('#checkIsSeriesToggleContainer').removeClass('col-md-6') + $('#checkIsSeriesToggleContainer').addClass('col-md-12') } function setViewForSeries(){ $(".startDatePicker").prop('required', false); $("#multipleOfferingTableDiv").removeClass('d-none'); - $("#eventLocation-main").removeClass('d-none'); + $("#eventLocation-main").show(); $("#inputEventLocation-main").prop('required', false); - $('#eventTime, #eventDate').addClass('d-none'); + $('#eventTime, #eventDate').hide(); $('#checkIsSeriesToggleContainer').removeClass('col-md-6') $('#checkIsSeriesToggleContainer').addClass('col-md-12') $("#pastDateWarningText").text("") @@ -93,6 +93,7 @@ function displayNotification(message) { }); } + function isDateInPast(dateString, timeString) { const combineDateTime = `${dateString}T${timeString}:00`; const setDate = new Date(combineDateTime).getTime(); From 2491d9fb475ef747eb9b467766ede0c810a94798 Mon Sep 17 00:00:00 2001 From: chapagainp Date: Wed, 9 Jul 2025 11:03:18 -0400 Subject: [PATCH 046/407] Assigning a variable in front of eventSessionNum --- app/static/js/createEvents.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 8c6cf3405..2df409d0c 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -497,7 +497,7 @@ let modalOpenedByEditButton = false; // #checkIsRepeating, #checkIsSeries are attributes for the toggle buttons on create event page $("#checkIsSeries, #edit_modal").click(function(event) { - eventSessionNum = 0; + let eventSessionNum = 0; // Set all modal location fields to the main location value (if needed) $('.multipleOfferingLocationField').val($('#inputEventLocation-main').val()); From e072911217656a2789523f3106c7728d010ee73f Mon Sep 17 00:00:00 2001 From: chapagainp Date: Wed, 9 Jul 2025 11:06:49 -0400 Subject: [PATCH 047/407] Removed the student users ability to create event --- app/controllers/admin/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 542704985..e48bd0fba 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -71,7 +71,7 @@ def switchUser(): @admin_bp.route('/eventTemplates') def templateSelect(): - if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff or g.current_user.isStudent: + if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff: allprograms = getAllowedPrograms(g.current_user) visibleTemplates = getAllowedTemplates(g.current_user) return render_template("/events/templateSelector.html", From a6ac5cff8f8fe5d9fadf8f3638f6256fd511b6d8 Mon Sep 17 00:00:00 2001 From: chapagainp Date: Wed, 9 Jul 2025 11:30:17 -0400 Subject: [PATCH 048/407] Add missing semicolons and clean up indentation and formatting --- app/static/js/createEvents.js | 335 +++++++++++++++++----------------- 1 file changed, 169 insertions(+), 166 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 2df409d0c..914f41134 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -21,9 +21,9 @@ function format12to24HourTime(timeStr) { let [hours, minutes] = timePart.split(":").map(Number); if (meridian === "PM" && hours !== 12) { - hours += 12; + hours += 12; } else if (meridian === "AM" && hours === 12) { - hours = 0; // midnight + hours = 0; // midnight } // format hours and minutes to always be 2 digits @@ -33,35 +33,36 @@ function format12to24HourTime(timeStr) { return `${formattedHours}:${formattedMinutes}`; } -function calculateRepeatingEventFrequency(){ - var eventDatesAndName = {name:$("#repeatingEventsNamePicker").val(), - isRepeating: true, - startDate:$("#repeatingEventsStartDate").val(), - endDate:$("#repeatingEventsEndDate").val(), - location:$("#repeatingEventsLocationPicker").val() || '' - } +function calculateRepeatingEventFrequency() { + var eventDatesAndName = { + name: $("#repeatingEventsNamePicker").val(), + isRepeating: true, + startDate: $("#repeatingEventsStartDate").val(), + endDate: $("#repeatingEventsEndDate").val(), + location: $("#repeatingEventsLocationPicker").val() || '' + } console.log(eventDatesAndName); $.ajax({ - type:"POST", + type: "POST", url: "/makeRepeatingEvents", //get the startDate, endDate and name as a dictionary data: eventDatesAndName, - success: function(jsonData){ + success: function (jsonData) { var generatedEvents = JSON.parse(jsonData) $("#generatedEventsTable tbody tr").remove(); - for(var event of generatedEvents){ + for (var event of generatedEvents) { loadRepeatingOfferingToModal(event) } $("#generatedEvents").removeClass("d-none"); }, - error: function(error){ + error: function (error) { console.log(error) displayNotification("Failed to generate events."); } }); } -function setViewForSingleOffering(){ +function setViewForSingleOffering() { $(".startDatePicker").prop('required', true); $("#multipleOfferingTableDiv").addClass('d-none'); $("#eventLocation-main").hide(); @@ -72,11 +73,11 @@ function setViewForSingleOffering(){ $('#checkIsSeriesToggleContainer').addClass('col-md-12') } -function setViewForSeries(){ +function setViewForSeries() { $(".startDatePicker").prop('required', false); $("#multipleOfferingTableDiv").removeClass('d-none'); $("#eventLocation-main").show(); - $("#inputEventLocation-main").prop('required', false); + $("#inputEventLocation-main").prop('required', false); $('#eventTime, #eventDate').hide(); $('#checkIsSeriesToggleContainer').removeClass('col-md-6') $('#checkIsSeriesToggleContainer').addClass('col-md-12') @@ -86,8 +87,8 @@ function setViewForSeries(){ function displayNotification(message) { $('#textNotifierPadding').addClass('pt-5'); $('.invalidFeedback').text(message); - $('.invalidFeedback').css('display', 'block'); - $('.invalidFeedback').on('animationend', function() { + $('.invalidFeedback').css('display', 'block'); + $('.invalidFeedback').on('animationend', function () { $('.invalidFeedback').css('display', 'none'); $('#textNotifierPadding').removeClass('pt-5') }); @@ -111,12 +112,12 @@ function initializeFlatpickr(obj) { minTime: "08:00", maxTime: "22:00", minuteIncrement: 15, - allowInput: true + allowInput: true }); } let eventSessionNum = 0; -function createOfferingModalRow({eventName=null, eventDate=null, startTime=null, endTime=null, eventLocation = null}={}){ +function createOfferingModalRow({ eventName = null, eventDate = null, startTime = null, endTime = null, eventLocation = null } = {}) { let clonedOffering = $("#multipleOfferingEvent").clone().removeClass('d-none').removeAttr("id"); const baseName = $('#inputEventName').val(); const fullName = baseName + ': session ' + (eventSessionNum + 1); @@ -124,11 +125,11 @@ function createOfferingModalRow({eventName=null, eventDate=null, startTime=null, // insert values for the newly created row - if (eventName) {clonedOffering.find('.multipleOfferingNameField').val(eventName)} - if (eventDate) {clonedOffering.find('.multipleOfferingDatePicker').val(eventDate)} - if (startTime) {clonedOffering.find('.multipleOfferingStartTime').val(startTime)} - if (endTime) {clonedOffering.find('.multipleOfferingEndTime').val(endTime)} - if (eventLocation) {clonedOffering.find('.multipleOfferingLocationField').val(eventLocation)} + if (eventName) { clonedOffering.find('.multipleOfferingNameField').val(eventName) } + if (eventDate) { clonedOffering.find('.multipleOfferingDatePicker').val(eventDate) } + if (startTime) { clonedOffering.find('.multipleOfferingStartTime').val(startTime) } + if (endTime) { clonedOffering.find('.multipleOfferingEndTime').val(endTime) } + if (eventLocation) { clonedOffering.find('.multipleOfferingLocationField').val(eventLocation) } eventSessionNum++; $("#multipleOfferingSlots").append(clonedOffering); @@ -163,7 +164,7 @@ function createOfferingModalRow({eventName=null, eventDate=null, startTime=null, return clonedOffering } -$('#saveSeries').on('click', function() { +$('#saveSeries').on('click', function () { //Requires that modal info updated before it can be saved, gives notifier if there are empty fields let eventOfferings = $('#multipleOfferingSlots .eventOffering'); let eventNameInputs = $('#multipleOfferingSlots .multipleOfferingNameField'); @@ -193,13 +194,13 @@ $('#saveSeries').on('click', function() { // Check if the date input field is empty datePickerInputs.each((index, datePickerInput) => { if (datePickerInput.value.trim() === '') { - isEmpty = true; - $(datePickerInput).addClass('border-red'); + isEmpty = true; + $(datePickerInput).addClass('border-red'); } else { - $(datePickerInput).removeClass('border-red'); + $(datePickerInput).removeClass('border-red'); } - }); - + }); + locationInputs.each((index, locationInput) => { if (locationInput.value.trim() === '') { isEmpty = true; @@ -210,16 +211,16 @@ $('#saveSeries').on('click', function() { }); // Check if the start time is after the end time - for(let i = 0; i < startTimeInputs.length; i++){ + for (let i = 0; i < startTimeInputs.length; i++) { let startTime = startTimeInputs[i].value let endTime = endTimeInputs[i].value - + if (navigator.userAgent.indexOf("Chrome") == -1) { startTime = format12to24HourTime(startTime) endTime = format12to24HourTime(endTime) } - if(startTime < endTime){ + if (startTime < endTime) { hasValidTimes = true; $(startTimeInputs[i]).removeClass('border-red'); $(endTimeInputs[i]).removeClass('border-red'); @@ -230,26 +231,26 @@ $('#saveSeries').on('click', function() { } } - if ($(dataTable).children().length < 1){ + if ($(dataTable).children().length < 1) { displayNotification("Please create events.") } // Check if there are duplicate event offerings let eventListings = {}; - for(let i = 0; i < eventOfferings.length; i++){ + for (let i = 0; i < eventOfferings.length; i++) { let eventName = eventNameInputs[i].value let date = datePickerInputs[i].value.trim() let startTime = startTimeInputs[i].value let eventListing = JSON.stringify([eventName, date, startTime, location]) - if (eventListing in eventListings){ // If we've seen this event before mark this event and the previous as duplicates + if (eventListing in eventListings) { // If we've seen this event before mark this event and the previous as duplicates hasDuplicateListings = true } else { // If we haven't seen this event before eventListings[eventListing] = i } } - if (isEmpty){ + if (isEmpty) { let emptyFieldMessage = "Event name, date or location field is empty"; displayNotification(emptyFieldMessage); } @@ -286,7 +287,7 @@ function updateEventNameField() { // if weekly, take the name of the first item (which is the same for all) and take the word 'week' let offeringText = $("#repeatingEventsNamePicker").val() $('#inputEventName').prop('placeholder', offeringText) - } + } } // Save the offerings from the modal to the hidden input field @@ -295,33 +296,34 @@ function saveOfferingsFromModal() { let isRepeatingStatus = $("#checkIsRepeating").is(":checked"); $("#formIsRepeating").prop("checked", isRepeatingStatus); let dataTable = isRepeatingStatus ? "#generatedEventsList" : "#multipleOfferingSlots"; - $(dataTable).children().each(function(index, element) { + $(dataTable).children().each(function (index, element) { let rowData; - if (isRepeatingStatus){ - rowData = $.map($(element).find("td"), function(td){ + if (isRepeatingStatus) { + rowData = $.map($(element).find("td"), function (td) { let input = $(td).find("input"); - if (input.length){ + if (input.length) { return input.val(); } else { return $(td).text().trim(); } - })} + }) + } else { rowData = $.map($(element).find("input"), (el) => $(el).val()); } - + let startTime = isRepeatingStatus ? $("#repeatingEventsStartTime").val() : rowData[3] let endTime = isRepeatingStatus ? $("#repeatingEventsEndTime").val() : rowData[4] if (navigator.userAgent.indexOf("Chrome") == -1) { - startTime = format12to24HourTime(startTime) - endTime = format12to24HourTime(endTime) + startTime = format12to24HourTime(startTime) + endTime = format12to24HourTime(endTime) } offerings.push({ - eventName: rowData[0], - eventLocation: isRepeatingStatus ? rowData[2] : rowData[1], - eventDate: isRepeatingStatus ? rowData[1] : rowData[2], - startTime: startTime, - endTime: endTime, + eventName: rowData[0], + eventLocation: isRepeatingStatus ? rowData[2] : rowData[1], + eventDate: isRepeatingStatus ? rowData[1] : rowData[2], + startTime: startTime, + endTime: endTime, }) }); @@ -331,13 +333,13 @@ function saveOfferingsFromModal() { $("#seriesData").val(offeringsJson); } -function verifyRepeatingFields(){ +function verifyRepeatingFields() { // verifies all fields in the repeating table are not empty. let repeatingFields = $(".repeatingEventsField"); let allFieldsFilled = true; - repeatingFields.each(function() { + repeatingFields.each(function () { let value = $(this).val(); - if (value === "" || value == null){ + if (value === "" || value == null) { allFieldsFilled = false; return false; } @@ -347,14 +349,14 @@ function verifyRepeatingFields(){ -function loadOfferingsToModal(){ +function loadOfferingsToModal() { let offerings = JSON.parse($("#seriesData").val()) - if (offerings.length < 1) {return;} + if (offerings.length < 1) { return; } let isRepeatingStatus = $("#checkIsRepeating").is(":checked"); let mainLocation = $('#inputEventLocation-main').val(); - if (isRepeatingStatus) {$("#generatedEvents").removeClass("d-none"); $("#generatedEventsTable tbody tr").remove();}; - offerings.forEach((offering, i) =>{ - if (isRepeatingStatus){ + if (isRepeatingStatus) { $("#generatedEvents").removeClass("d-none"); $("#generatedEventsTable tbody tr").remove(); }; + offerings.forEach((offering, i) => { + if (isRepeatingStatus) { loadRepeatingOfferingToModal(offering); } else { let newOfferingModalRow = createOfferingModalRow({ @@ -362,20 +364,21 @@ function loadOfferingsToModal(){ eventLocation: offering.eventLocation || mainLocation }); //stripes odd event sections in event modal - newOfferingModalRow.css('background-color', i % 2 ?'#f2f2f2':'#fff'); - }}); + newOfferingModalRow.css('background-color', i % 2 ? '#f2f2f2' : '#fff'); + } + }); } -function loadRepeatingOfferingToModal(offering){ +function loadRepeatingOfferingToModal(offering) { var seriesTable = $("#generatedEventsTable"); var eventDate = new Date(offering.date || offering.eventDate).toLocaleDateString(); console.log(offering); - + seriesTable.append( "" + - "" + (offering.name || offering.eventName) + "" + + "" + (offering.name || offering.eventName) + "" + "" + eventDate + "" + - "" + (offering.eventLocation || offering.location)+ "" + + "" + (offering.eventLocation || offering.location) + "" + "
" + "" ); @@ -386,19 +389,19 @@ function updateOfferingsTable() { let offerings = JSON.parse($("#seriesData").val()) var offeringsTable = $("#offeringsTable"); offeringsTable.find("tbody tr").remove(); // Clear existing rows - offerings.forEach(function(offering){ + offerings.forEach(function (offering) { //format to 12hr time for display var formattedEventDate = formatDate(offering.eventDate); var startTime = format24to12HourTime(offering.startTime); var endTime = format24to12HourTime(offering.endTime); offeringsTable.append(`` + - "" + offering.eventName + "" + - "" + formattedEventDate + "" + - "" + startTime + "" + - "" + endTime + "" + - "" + (offering.eventLocation || offering.location || "") + "" + - "" - ); + "" + offering.eventName + "" + + "" + formattedEventDate + "" + + "" + startTime + "" + + "" + endTime + "" + + "" + (offering.eventLocation || offering.location || "") + "" + + "" + ); }); } @@ -415,7 +418,7 @@ function formatDate(originalDate) { /* * Run when the webpage is ready for javascript */ -$(document).ready(function() { +$(document).ready(function () { var isEditPage = (window.location.pathname == '/event/' + $('#newEventID').val() + '/edit') //makes sure bonners toggle will stay on between event pages @@ -428,7 +431,7 @@ $(document).ready(function() { // don't use a minimum if we are editing an existing event var minDate = new Date() if (isEditPage) { - minDate = null; + minDate = null; } // Initialize datepicker with proper options @@ -442,7 +445,7 @@ $(document).ready(function() { minDate: minDate }); - $(".datePicker").each(function(idx, el) { + $(".datePicker").each(function (idx, el) { var dateStr = $(el).val(); if (dateStr) { var dateObj = new Date(dateStr); @@ -476,75 +479,75 @@ $(document).ready(function() { //check if user has selected a toggle, cancel form submission if not let isAllVolunteer = $("#pageTitle").text() == 'Create All Volunteer Training' - if(trainingStatus || serviceHourStatus || engagementStatus || bonnersStatus || isAllVolunteer) { + if (trainingStatus || serviceHourStatus || engagementStatus || bonnersStatus || isAllVolunteer) { // Disable button when we are ready to submit $(this).find("input[type=submit]").prop("disabled", true); } else { msgFlash("You must indicate whether the event is a training, is an engagement, earns service hours, or is a Bonners Scholars event!", "danger"); event.preventDefault(); - } + } }); updateOfferingsTable(); - - if ($("#checkIsSeries").is(":checked")){ + + if ($("#checkIsSeries").is(":checked")) { setViewForSeries(); } - -let modalOpenedByEditButton = false; -// #checkIsRepeating, #checkIsSeries are attributes for the toggle buttons on create event page -$("#checkIsSeries, #edit_modal").click(function(event) { - let eventSessionNum = 0; - // Set all modal location fields to the main location value (if needed) - $('.multipleOfferingLocationField').val($('#inputEventLocation-main').val()); + let modalOpenedByEditButton = false; + // #checkIsRepeating, #checkIsSeries are attributes for the toggle buttons on create event page - let isSeries = $("#checkIsSeries").is(":checked") - modalOpenedByEditButton = ($(this).attr('id') === 'edit_modal'); + $("#checkIsSeries, #edit_modal").click(function (event) { + let eventSessionNum = 0; + // Set all modal location fields to the main location value (if needed) + $('.multipleOfferingLocationField').val($('#inputEventLocation-main').val()); - if (isSeries) { - if ($('#inputEventName').val().trim() == '') { - $('#checkIsSeries').prop('checked', false) - $('#inputEventName').focus(); - return - } - if ($('#inputEventLocation-main').val().trim() == '') { - $('#checkIsSeries').prop('checked', false) - $('#inputEventLocation-main').focus(); - return - } - setViewForSeries(); - loadOfferingsToModal(); - $('#modalSeries').modal('show'); + let isSeries = $("#checkIsSeries").is(":checked"); + modalOpenedByEditButton = ($(this).attr('id') === 'edit_modal'); - // Disable single event name and location fields - $('#inputEventName').prop('readonly', true) - $('#inputEventLocation-main').prop('readonly', true) - } else { - setViewForSingleOffering() - $('#multipleOfferingTableDiv').addClass('d-none'); - // Enable single event name and location fields - $('#inputEventName').prop('readonly', false) - $('#inputEventName').prop('placeholder', 'Enter event name') - $('#inputEventLocation-main').prop('readonly', false) - $('#inputEventLocation-main').prop('placeholder', 'Enter event location') - } -}); + if (isSeries) { + if ($('#inputEventName').val().trim() == '') { + $('#checkIsSeries').prop('checked', false); + $('#inputEventName').focus(); + return; + } + if ($('#inputEventLocation-main').val().trim() == '') { + $('#checkIsSeries').prop('checked', false); + $('#inputEventLocation-main').focus(); + return; + } + setViewForSeries(); + loadOfferingsToModal(); + $('#modalSeries').modal('show'); + + // Disable single event name and location fields + $('#inputEventName').prop('readonly', true); + $('#inputEventLocation-main').prop('readonly', true); + } else { + setViewForSingleOffering(); + $('#multipleOfferingTableDiv').addClass('d-none'); + // Enable single event name and location fields + $('#inputEventName').prop('readonly', false); + $('#inputEventName').prop('placeholder', 'Enter event name'); + $('#inputEventLocation-main').prop('readonly', false); + $('#inputEventLocation-main').prop('placeholder', 'Enter event location'); + } + }); //untoggles the button when the modal cancel or close button is clicked - $("#cancelModalPreview, #multipleOfferingXbutton").click(function(){ + $("#cancelModalPreview, #multipleOfferingXbutton").click(function () { if (modalOpenedByEditButton == false) { $('#modalSeries').modal('hide'); $("#checkIsSeries").prop('checked', false); - setViewForSingleOffering() + setViewForSingleOffering(); } - pendingmultipleEvents.forEach(function(element){ + pendingmultipleEvents.forEach(function (element) { element.remove(); }); let isSeries = $("#checkIsSeries").is(":checked") - if (!isSeries){ + if (!isSeries) { // Enable single event name field $('#inputEventName').prop('readonly', false) $('#inputEventName').prop('placeholder', 'Enter event name') @@ -554,19 +557,19 @@ $("#checkIsSeries, #edit_modal").click(function(event) { } }); - $("#checkIsRepeating").change(function() { + $("#checkIsRepeating").change(function () { if ($(this).is(':checked')) { $('.addMultipleOfferingEvent').hide(); $("#repeatingEventsDiv").removeClass('d-none'); $("#multipleOfferingSlots").children().remove(); $("#multipleOfferingSlots").addClass('d-none'); } else { - $('.addMultipleOfferingEvent').show(); + $('.addMultipleOfferingEvent').show(); $("#repeatingEventsDiv").addClass('d-none'); $("#multipleOfferingSlots").removeClass('d-none'); } }); - + $("#repeatingEventsDiv").change(handleRepeatingEventsChange) function handleRepeatingEventsChange() { @@ -585,13 +588,13 @@ $("#checkIsSeries, #edit_modal").click(function(event) { if (endDate <= startDate) { displayNotification("The end date must be after the start date."); - table.each(function(){$(this).remove()}) + table.each(function () { $(this).remove() }) $("#generatedEvents").addClass('d-none'); return; } - if (endTime <= startTime){ + if (endTime <= startTime) { displayNotification("The end time must be after the start time."); - table.each(function(){$(this).remove()}) + table.each(function () { $(this).remove() }) $("#generatedEvents").addClass('d-none'); return; } @@ -600,56 +603,56 @@ $("#checkIsSeries, #edit_modal").click(function(event) { } } - $(document).on("click", ".deleteGeneratedEvent, .deleteMultipleOffering", function() { + $(document).on("click", ".deleteGeneratedEvent, .deleteMultipleOffering", function () { let attachedRow = $(this).closest(".eventOffering") attachedRow.animate({ opacity: 0, - }, 500, function() { - // After the animation completes, remove the row - attachedRow.remove(); + }, 500, function () { + // After the animation completes, remove the row + attachedRow.remove(); }); }); - + /*cloning the div with ID multipleOfferingEvent and cloning, changing the ID of each clone going up by 1. This also changes the ID of the deleteMultipleOffering so that when the trash icon is clicked, that specific row will be deleted*/ - $(".addMultipleOfferingEvent").click(function() { - // Get the current value from the main location input - let mainLocation = $("#inputEventLocation-main").val(); - createOfferingModalRow({eventLocation: mainLocation}); -}); + $(".addMultipleOfferingEvent").click(function () { + // Get the current value from the main location input + let mainLocation = $("#inputEventLocation-main").val(); + createOfferingModalRow({ eventLocation: mainLocation }); + }); - var minDate = new Date('10/25/1999') - $("#startDatePicker-main").datepicker("option", "minDate", minDate) + var minDate = new Date('10/25/1999') + $("#startDatePicker-main").datepicker("option", "minDate", minDate) // This converts the time to 24 hour format in case it is in 12 hour format (like in Firefox) -function handleTimeFormatting(timeArray){ - let time = timeArray[0] - let timeSuffix = timeArray[1] // looks for AM or PM in time - let [hours , min] = time. split(':') - - if (timeArray.length === 2) { - hours = parseInt(hours, 10) - if (timeSuffix === 'PM' && hours !== 12) { - hours += 12; - } else if (timeSuffix === 'AM' && hours === 12) { - hours = 0; + function handleTimeFormatting(timeArray) { + let time = timeArray[0] + let timeSuffix = timeArray[1] // looks for AM or PM in time + let [hours, min] = time.split(':') + + if (timeArray.length === 2) { + hours = parseInt(hours, 10) + if (timeSuffix === 'PM' && hours !== 12) { + hours += 12; + } else if (timeSuffix === 'AM' && hours === 12) { + hours = 0; + } + const hoursStr = hours.toString().padStart(2, '0'); + return [hoursStr, min] } - const hoursStr = hours.toString().padStart(2, '0'); - return [hoursStr, min] + return [hours, min] } - return [hours, min] -} function checkIfDateInPast() { const [month, day, year] = $("#startDatePicker-main").val().split('/') - const startTimeArray = $("#startTime-main").val().split(' ') + const startTimeArray = $("#startTime-main").val().split(' ') const [startHour, startMin] = handleTimeFormatting(startTimeArray) const endTimeArray = $('#endTime-main').val().split(' ') - const [endHour, endMin] = handleTimeFormatting (endTimeArray) - let startDateSelected =new Date(+year, +month - 1, +day, +startHour, +startMin); + const [endHour, endMin] = handleTimeFormatting(endTimeArray) + let startDateSelected = new Date(+year, +month - 1, +day, +startHour, +startMin); let endDateSelected = new Date(+year, +month - 1, +day, +endHour, +endMin) let now = new Date() - + if (startDateSelected < now && endDateSelected > now) { $("#pastDateWarningText").text("This event is currently in progress!") @@ -657,26 +660,26 @@ function handleTimeFormatting(timeArray){ else if (startDateSelected < now && endDateSelected < now) { $("#pastDateWarningText").text("This event is in the past!") } - else + else $("#pastDateWarningText").text("") } - $("#startDatePicker-main").on("change", function() { + $("#startDatePicker-main").on("change", function () { checkIfDateInPast() }) - $("#startTime-main").on("change", function() { + $("#startTime-main").on("change", function () { checkIfDateInPast() }) - - $("#endTime-main").on("change", function() { + + $("#endTime-main").on("change", function () { checkIfDateInPast() }) // everything except Chrome if (navigator.userAgent.indexOf("Chrome") == -1) { initializeFlatpickr(".flatpickr") - + $(".timepicker").prop("type", "text"); $(".timeIcons").prop("hidden", false); @@ -694,7 +697,7 @@ function handleTimeFormatting(timeArray){ $(".datePicker").datepicker("option", "disabled", true); } - $(".readonly").on('keydown paste', function(e) { + $(".readonly").on('keydown paste', function (e) { if (e.keyCode != 9) // ignore tab e.preventDefault(); }); @@ -742,7 +745,7 @@ function handleTimeFormatting(timeArray){ setCharacterLimit(this, "#remainingCharacters"); }); - setCharacterLimit($("#inputCharacters"), "#remainingCharacters"); + setCharacterLimit($("#inputCharacters"), "#remainingCharacters"); }); From 5d7f8fe8d2cab7e471226f6acb46e919b6147805 Mon Sep 17 00:00:00 2001 From: chapagainp Date: Wed, 9 Jul 2025 14:59:04 -0400 Subject: [PATCH 049/407] prefill event name and location from main input --- app/static/js/createEvents.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 914f41134..0ee2d664c 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -559,6 +559,8 @@ $(document).ready(function () { $("#checkIsRepeating").change(function () { if ($(this).is(':checked')) { + $("#repeatingEventsNamePicker").val($("#inputEventName").val()); + $("#repeatingEventsLocationPicker").val($("#inputEventLocation-main").val()); $('.addMultipleOfferingEvent').hide(); $("#repeatingEventsDiv").removeClass('d-none'); $("#multipleOfferingSlots").children().remove(); From 52dbcfc38cfd4c2582a9dfac9ab93b9f173794dd Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Thu, 17 Jul 2025 08:57:07 -0400 Subject: [PATCH 050/407] removed the debug output --- app/static/js/createEvents.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 0ee2d664c..fcd1d3f8c 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -41,7 +41,6 @@ function calculateRepeatingEventFrequency() { endDate: $("#repeatingEventsEndDate").val(), location: $("#repeatingEventsLocationPicker").val() || '' } - console.log(eventDatesAndName); $.ajax({ type: "POST", url: "/makeRepeatingEvents", @@ -372,7 +371,7 @@ function loadOfferingsToModal() { function loadRepeatingOfferingToModal(offering) { var seriesTable = $("#generatedEventsTable"); var eventDate = new Date(offering.date || offering.eventDate).toLocaleDateString(); - console.log(offering); + seriesTable.append( "" + From 540bd02b01ffc68e832670272087754a6959e264 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Thu, 17 Jul 2025 11:26:47 -0400 Subject: [PATCH 051/407] Removed another debug out put --- app/static/js/createEvents.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index fcd1d3f8c..2c3073733 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -41,6 +41,7 @@ function calculateRepeatingEventFrequency() { endDate: $("#repeatingEventsEndDate").val(), location: $("#repeatingEventsLocationPicker").val() || '' } + $.ajax({ type: "POST", url: "/makeRepeatingEvents", @@ -55,7 +56,7 @@ function calculateRepeatingEventFrequency() { $("#generatedEvents").removeClass("d-none"); }, error: function (error) { - console.log(error) + displayNotification("Failed to generate events."); } }); @@ -371,7 +372,7 @@ function loadOfferingsToModal() { function loadRepeatingOfferingToModal(offering) { var seriesTable = $("#generatedEventsTable"); var eventDate = new Date(offering.date || offering.eventDate).toLocaleDateString(); - + seriesTable.append( "" + From f4b03d4885fae70082abfa8e1ab70864376f8c1e Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Thu, 17 Jul 2025 16:05:09 -0400 Subject: [PATCH 052/407] add input handling for name, location, and dates --- app/static/js/createEvents.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 2c3073733..87e444a60 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -230,10 +230,7 @@ $('#saveSeries').on('click', function () { $(endTimeInputs[i]).addClass('border-red'); } } - - if ($(dataTable).children().length < 1) { - displayNotification("Please create events.") - } + let noEventsCreated = ($(dataTable).children().length < 1); // Check if there are duplicate event offerings let eventListings = {}; @@ -249,8 +246,9 @@ $('#saveSeries').on('click', function () { eventListings[eventListing] = i } } - - if (isEmpty) { + if(noEventsCreated) { + displayNotification("Please create events."); + } else if (isEmpty) { let emptyFieldMessage = "Event name, date or location field is empty"; displayNotification(emptyFieldMessage); } @@ -571,8 +569,14 @@ $(document).ready(function () { $("#multipleOfferingSlots").removeClass('d-none'); } }); - - $("#repeatingEventsDiv").change(handleRepeatingEventsChange) + + $("#repeatingEventsNamePicker, " + "#repeatingEventsLocationPicker").on("input", handleRepeatingEventsChange); + + $("#repeatingEventsStartDate, " + + "#repeatingEventsEndDate, " + + "#repeatingEventsStartTime, " + + "#repeatingEventsEndTime" + ).on("change", handleRepeatingEventsChange); function handleRepeatingEventsChange() { if (verifyRepeatingFields()) { From fb03c6c3dd41730000dcc61fb40fd24854681059 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Fri, 18 Jul 2025 09:41:04 -0400 Subject: [PATCH 053/407] set 7-second timer for repeating event updates --- app/static/js/createEvents.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 87e444a60..f46afdea5 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -418,7 +418,15 @@ function formatDate(originalDate) { */ $(document).ready(function () { var isEditPage = (window.location.pathname == '/event/' + $('#newEventID').val() + '/edit') - + + // This is to prevent the server from being overloaded with requests while the user is typing + let debounceTimer; + $("#repeatingEventsNamePicker, #repeatingEventsLocationPicker").on("input", function () { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(function () { + handleRepeatingEventsChange(); + }, 7000); + }); //makes sure bonners toggle will stay on between event pages if (isEditPage) { if ($("#checkBonners")) { From fccccb83c087472e2a590e9e99303fda2dcf426d Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Fri, 18 Jul 2025 10:06:27 -0400 Subject: [PATCH 054/407] Change passing location as a list to a string --- tests/code/test_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/code/test_events.py b/tests/code/test_events.py index 4065c7017..c1f75bd39 100644 --- a/tests/code/test_events.py +++ b/tests/code/test_events.py @@ -300,7 +300,7 @@ def test_calculateRecurringEventFrequency(): eventInfo = {'name': "testEvent", 'startDate': parser.parse("02/22/2023"), 'endDate': parser.parse("03/11/2023"), - 'location':("a big room")} + 'location': "a big room"} # test correct response returnedEvents = getRepeatingEventsData(eventInfo) From a23aa23191aa72025d66264bf8cf80782f1f11fc Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Fri, 18 Jul 2025 11:09:55 -0400 Subject: [PATCH 055/407] set the timer to 7 secs instead of 3 --- app/static/js/createEvents.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index f46afdea5..599b46684 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -419,6 +419,7 @@ function formatDate(originalDate) { $(document).ready(function () { var isEditPage = (window.location.pathname == '/event/' + $('#newEventID').val() + '/edit') + // This is to prevent the server from being overloaded with requests while the user is typing let debounceTimer; $("#repeatingEventsNamePicker, #repeatingEventsLocationPicker").on("input", function () { From 2f6e925ac109e7f303f159b8d679af688948c01c Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Sun, 20 Jul 2025 14:03:51 -0400 Subject: [PATCH 056/407] changed the timer to 3000 milliseconds --- app/static/js/createEvents.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 0027521b4..16a7362ce 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -516,8 +516,6 @@ function checkValidation() { } } - - /* * Run when the webpage is ready for javascript */ @@ -531,7 +529,7 @@ $(document).ready(function () { clearTimeout(debounceTimer); debounceTimer = setTimeout(function () { handleRepeatingEventsChange(); - }, 7000); + }, 3000); }); //makes sure bonners toggle will stay on between event pages if (isEditPage) { @@ -671,8 +669,14 @@ $("#cancelEvent").on('click', function (event) { $("#multipleOfferingSlots").removeClass('d-none'); } }); + + $("#repeatingEventsNamePicker, " + "#repeatingEventsLocationPicker").on("input", handleRepeatingEventsChange); - $("#repeatingEventsDiv").change(handleRepeatingEventsChange) + $("#repeatingEventsStartDate, " + + "#repeatingEventsEndDate, " + + "#repeatingEventsStartTime, " + + "#repeatingEventsEndTime" + ).on("change", handleRepeatingEventsChange); // this handels start date, end date, last event date, start time, and end time function handleRepeatingEventsChange() { if (!verifyRepeatingFields()) { From 620728bab8fffc0a400612f9a3f97ac279d2bbd7 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Mon, 21 Jul 2025 10:16:54 -0400 Subject: [PATCH 057/407] fixed Assertion Issue in the test --- tests/code/test_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/code/test_search.py b/tests/code/test_search.py index bea7c05fb..837a3f9cf 100644 --- a/tests/code/test_search.py +++ b/tests/code/test_search.py @@ -35,7 +35,7 @@ def test_searchUsers(): searchResults = searchUsers('sa') assert len(searchResults) == 2 assert searchResults['lamichhanes2'] == model_to_dict(User.get_by_id('lamichhanes2')) - assert searchResults["sawconc"] == model_to_dict(secondUser) + assert searchResults["sawconc"] == model_to_dict (User.get_by_id('sawconc')) assert '(555)555-5555' in searchResults["lamichhanes2"].values() transaction.rollback() From 73d0d2cbb14398f112f70678063655519d68963b Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Mon, 21 Jul 2025 15:53:22 -0400 Subject: [PATCH 058/407] fixed indentation --- app/static/js/createEvents.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 16a7362ce..00acf4076 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -671,12 +671,10 @@ $("#cancelEvent").on('click', function (event) { }); $("#repeatingEventsNamePicker, " + "#repeatingEventsLocationPicker").on("input", handleRepeatingEventsChange); - $("#repeatingEventsStartDate, " + - "#repeatingEventsEndDate, " + + "#repeatingEventsEndDate, " + "#repeatingEventsStartTime, " + - "#repeatingEventsEndTime" - ).on("change", handleRepeatingEventsChange); + "#repeatingEventsEndTime").on("change", handleRepeatingEventsChange); // this handels start date, end date, last event date, start time, and end time function handleRepeatingEventsChange() { if (!verifyRepeatingFields()) { From f418eb06a3a7b13d488726da5ff6044a5a3ade18 Mon Sep 17 00:00:00 2001 From: Joyce J Nimely Date: Mon, 21 Jul 2025 16:26:15 -0400 Subject: [PATCH 059/407] fixed else statement --- app/static/js/createEvents.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 00acf4076..943103e7c 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -761,11 +761,9 @@ $("#cancelEvent").on('click', function (event) { if (startDateSelected < now && endDateSelected > now) { $("#pastDateWarningText").text("This event is currently in progress!") - } - else if (startDateSelected < now && endDateSelected < now) { + } else if (startDateSelected < now && endDateSelected < now) { $("#pastDateWarningText").text("This event is in the past!") - } - else + }else{ $("#pastDateWarningText").text("") } @@ -797,6 +795,7 @@ $("#cancelEvent").on('click', function (event) { $(".timepicker").prop("type", "time"); $(".timeIcons").prop("hidden", true); } + } if ($(".datePicker").is("readonly")) { $(".datePicker").datepicker("option", "disabled", true); @@ -851,8 +850,9 @@ $("#cancelEvent").on('click', function (event) { }); setCharacterLimit($("#inputCharacters"), "#remainingCharacters"); - -}); + }); + + From cb6d5c3f68064db77d6ee4d42c064e485a626fbc Mon Sep 17 00:00:00 2001 From: zawn Date: Wed, 8 Oct 2025 18:57:54 -0400 Subject: [PATCH 060/407] merging into main --- app/static/js/createEvents.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 0f82bacc5..0a6ef09f5 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -326,18 +326,12 @@ function updateEventNameField() { if (!isSeries) { // if not weeekly, add them to a set to remove duplicates, then put them in a string to populate the field - let names = new Set() - offerings.forEach(offering => { - names.add(offering.eventName) - }); - let offeringsText = Array.from(names).join(", ") - $('#inputEventName').val(offeringsText) + $('#inputEventName').prop('placeholder', '') } else { // if weekly, take the name of the first item (which is the same for all) and take the word 'week' let offeringText = $("#repeatingEventsNamePicker").val() - $('#inputEventName').val(offeringText) - } + $('#inputEventName').prop('placeholder', offeringText) } } // Save the offerings from the modal to the hidden input field From 3ec0ed5cbcb3b4e5ec199d96ed8d36702642f146 Mon Sep 17 00:00:00 2001 From: makindeo Date: Sun, 9 Nov 2025 00:30:24 -0500 Subject: [PATCH 061/407] Fixed failing tests by improving parts of the logic. test_removeProposal is still failing, and it needs to be fixed. --- app/logic/fileHandler.py | 1 - tests/code/test_minor.py | 81 +++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/app/logic/fileHandler.py b/app/logic/fileHandler.py index 87d9a7dcf..0ec43d86f 100644 --- a/app/logic/fileHandler.py +++ b/app/logic/fileHandler.py @@ -88,7 +88,6 @@ def saveFiles(self, saveOriginalFile=None): elif self.proposalId: attachmentName = str(saveOriginalFile.id) + "/" + file.filename - isFileInProposal = AttachmentUpload.select().where(AttachmentUpload.proposal == self.proposalId, AttachmentUpload.fileName == attachmentName).exists() if not isFileInProposal: diff --git a/tests/code/test_minor.py b/tests/code/test_minor.py index 8bc4065ad..4e104307b 100644 --- a/tests/code/test_minor.py +++ b/tests/code/test_minor.py @@ -6,6 +6,7 @@ from collections import OrderedDict from playhouse.shortcuts import model_to_dict from werkzeug.datastructures import ImmutableMultiDict, FileStorage +from types import SimpleNamespace from app import app from app.models import mainDB @@ -106,9 +107,17 @@ def testProposal(request): 'totalWeeks': params.get("totalWeeks", 10), 'experienceDescription': params.get("experienceDescription", "Working day and night to make sure Finn's needs are met"), 'status': params.get("status", 'Draft'), - } + } + mockRequestProposalObject = SimpleNamespace( + form=defaultProposal, + files=SimpleNamespace( + getlist=lambda key: [], + get=lambda key: None + ) + ) + # override default values with those put in the parameters. - return defaultProposal + return mockRequestProposalObject @pytest.mark.integration def test_getCourseInformation(testUser): @@ -205,16 +214,16 @@ def test_getCCEMinorProposals(testUser, testProposal): assert len(getCCEMinorProposals(testUser.username)) == 1 # convert the otherEngagement to a summerExperience proposal type - testProposal.pop("experienceName") - testProposal.pop("experienceDescription") + testProposal.form.pop("experienceName") + testProposal.form.pop("experienceDescription") - testProposal["roleDescription"] = "Assistant to Finn" - testProposal["experienceType"] = "Internship" - testProposal["contentArea"] = ["Power and inequality", "Civic literacy"] + testProposal.form["roleDescription"] = "Assistant to Finn" + testProposal.form["experienceType"] = "Internship" + testProposal.form["contentArea"] = ["Power and inequality", "Civic literacy"] with app.app_context(): g.current_user = testUser.username - createSummerExperience(testUser.username, ImmutableMultiDict(testProposal)) + createSummerExperience(testUser.username, ImmutableMultiDict(testProposal.form)) assert len(getCCEMinorProposals(testUser.username)) == 2 @@ -526,11 +535,18 @@ def test_getMinorProgress(): 'experienceDescription': 'Test Description', "status": "Draft" } + khattsRequestedEngagementRequest = SimpleNamespace( + form=khattsRequestedEngagement, + files=SimpleNamespace( + getlist=lambda key: [], + get=lambda key: None + ) + ) # verify that Sreynit has a summer, 1 engagement, and an other community engagement request in with app.app_context(): g.current_user = "ramsayb2" - createOtherEngagement("khatts", khattsRequestedEngagement) + createOtherEngagement("khatts", khattsRequestedEngagementRequest) createSummerExperience("khatts", khattsSummerExperience) minorProgressWithSummerAndRequestOther = getMinorProgress() @@ -546,7 +562,7 @@ def test_createSummerExperience(testUser, testTerm, testProposal): with mainDB.atomic() as transaction: # create testing objects - testProposal["term"] = testTerm + testProposal.form["term"] = testTerm User.create(username="glek", firstName="kafui", @@ -562,7 +578,7 @@ def test_createSummerExperience(testUser, testTerm, testProposal): # create the summer experience with the test data and verify FINN has a new entry with app.app_context(): g.current_user = "glek" - createSummerExperience(testUser.username, ImmutableMultiDict(testProposal)) + createSummerExperience(testUser.username, ImmutableMultiDict(testProposal.form)) newSummerExperiences = list(CCEMinorProposal.select().where(CCEMinorProposal.student == testUser.username, CCEMinorProposal.proposalType == 'Summer Experience')) assert len(newSummerExperiences) == 1 @@ -617,19 +633,22 @@ def test_updateOtherEngagementRequest(testUser, testProposal): createdOtherEngagementRequest = None with app.app_context(): g.current_user = "glek" - createdOtherEngagementRequest = createOtherEngagement(user.username, ImmutableMultiDict(testProposal)) - + createOtherEngagement(user.username, testProposal) + createdOtherEngagementRequest = CCEMinorProposal.select().where( + CCEMinorProposal.student == user, + CCEMinorProposal.proposalType == "Other Engagement" + ).get() proposalID = createdOtherEngagementRequest.id assert createdOtherEngagementRequest.experienceName == "Assistant to Finn" assert createdOtherEngagementRequest.orgName == "Finn's Assistants" assert createdOtherEngagementRequest.experienceDescription == "Catering to Finn's every need" - testProposal["experienceName"] = "Opponent to Finn" - testProposal["orgName"] = "Finn's Ops" - testProposal["experienceDescription"] = "Hating on Finn 24/7" + testProposal.form["experienceName"] = "Opponent to Finn" + testProposal.form["orgName"] = "Finn's Ops" + testProposal.form["experienceDescription"] = "Hating on Finn 24/7" - updateOtherEngagementRequest(proposalID, ImmutableMultiDict(testProposal)) + updateOtherEngagementRequest(proposalID, testProposal) updatedProposal = CCEMinorProposal.get_by_id(proposalID) assert updatedProposal.experienceName == "Opponent to Finn" @@ -646,19 +665,21 @@ def test_removeProposal(testProposal, testUser): '''creates a test course with all foreign key fields. tests if they can be deleted''' - testProposalId = 999 - with mainDB.atomic() as transaction: - - assert list(CCEMinorProposal.select(CCEMinorProposal.id).where(CCEMinorProposal.id == testProposalId)) == [] - - - testOtherEngagement = CCEMinorProposal.create(id=testProposalId, + testOtherEngagement = CCEMinorProposal.create( student = testUser.username, proposalType = 'Other Engagement', createdBy = testUser.username, - **testProposal + **testProposal.form ) + + testProposal = CCEMinorProposal.select().where( + CCEMinorProposal.student == testUser, + CCEMinorProposal.proposalType == "Other Engagement" + ).get() + + testProposalId = testProposal.id + assert list(CCEMinorProposal.select().where(CCEMinorProposal.id == testProposalId)) == [testOtherEngagement] # creates a base object for proposal events @@ -667,7 +688,7 @@ def test_removeProposal(testProposal, testUser): handledProposalFile = FileHandler(proposalFileStorageObject, proposalId=testProposalId) # uploading a file to proposalattachments - handledProposalFile.saveFiles() + handledProposalFile.saveFiles(testProposalId) try: assert AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == f"{testProposalId}.pdf").exists() @@ -714,17 +735,17 @@ def test_updateSummerExperience(testUser, testProposal): createdSummerExperience = None with app.app_context(): g.current_user = "glek" - createdSummerExperience = createSummerExperience(user.username, ImmutableMultiDict(testProposal)) + createdSummerExperience = createSummerExperience(user.username, ImmutableMultiDict(testProposal.form)) proposalID = createdSummerExperience.id assert createdSummerExperience.totalHours == 301 assert createdSummerExperience.experienceType == "Internship" - testProposal["experienceType"] = "Not an internship" - testProposal["totalHours"] = 201 + testProposal.form["experienceType"] = "Not an internship" + testProposal.form["totalHours"] = 201 - updateSummerExperience(proposalID, ImmutableMultiDict(testProposal)) + updateSummerExperience(proposalID, ImmutableMultiDict(testProposal.form)) updatedProposal = CCEMinorProposal.get_by_id(proposalID) From cff1d8d6927afe1741248c0559a6c65d4e1262c9 Mon Sep 17 00:00:00 2001 From: makindeo Date: Sun, 9 Nov 2025 13:13:08 -0500 Subject: [PATCH 062/407] All tests fixed. Will now begin improving implementation logic. --- tests/code/test_minor.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/code/test_minor.py b/tests/code/test_minor.py index 4e104307b..160ade140 100644 --- a/tests/code/test_minor.py +++ b/tests/code/test_minor.py @@ -673,26 +673,28 @@ def test_removeProposal(testProposal, testUser): **testProposal.form ) - testProposal = CCEMinorProposal.select().where( + testProposalObject = CCEMinorProposal.select().where( CCEMinorProposal.student == testUser, CCEMinorProposal.proposalType == "Other Engagement" ).get() - testProposalId = testProposal.id + testFileName = "proposal.pdf" + testProposalId = testProposalObject.id + testFullFileName = f"{testProposalId}/{testFileName}" assert list(CCEMinorProposal.select().where(CCEMinorProposal.id == testProposalId)) == [testOtherEngagement] # creates a base object for proposal events - proposalFileStorageObject = [FileStorage(filename= "proposal.pdf")] + proposalFileStorageObject = [FileStorage(filename=testFileName)] handledProposalFile = FileHandler(proposalFileStorageObject, proposalId=testProposalId) # uploading a file to proposalattachments - handledProposalFile.saveFiles(testProposalId) + handledProposalFile.saveFiles(testProposalObject) try: - assert AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == f"{testProposalId}.pdf").exists() - assert 1 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == f"{testProposalId}.pdf").count() + assert AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).exists() + assert 1 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).count() with app.app_context(): g.current_user = testUser.username @@ -700,15 +702,15 @@ def test_removeProposal(testProposal, testUser): assert list(CCEMinorProposal.select().where(CCEMinorProposal.id == testProposalId)) == [] - assert not AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == f"{testProposalId}.pdf").exists() - assert 0 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == f"{testProposalId}.pdf").count() + assert not AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).exists() + assert 0 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).count() except Exception as e: raise e finally: fileExists = AttachmentUpload.get_or_none(proposal_id = testProposalId) - fullFilePath = handledProposalFile.getFileFullPath(f'{testProposalId}.pdf') + fullFilePath = handledProposalFile.getFileFullPath(testFullFileName) if fileExists: os.remove(fullFilePath) From f9baba53101d2c579f802e609002f434c156d03d Mon Sep 17 00:00:00 2001 From: makindeo Date: Sun, 9 Nov 2025 15:13:06 -0500 Subject: [PATCH 063/407] Removed unnecessary exception catching across created files. --- app/controllers/admin/routes.py | 1 - app/logic/fileHandler.py | 2 +- app/logic/minor.py | 39 +++++++++++++-------------------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/app/controllers/admin/routes.py b/app/controllers/admin/routes.py index 14ea616b0..0694d0a16 100644 --- a/app/controllers/admin/routes.py +++ b/app/controllers/admin/routes.py @@ -316,7 +316,6 @@ def eventDisplay(eventId): futureTerms = selectSurroundingTerms(g.current_term) userHasRSVPed = checkUserRsvp(g.current_user, event) filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments) - print(filepaths) isProgramManager = g.current_user.isProgramManagerFor(eventData['program']) requirements, bonnerCohorts = [], [] diff --git a/app/logic/fileHandler.py b/app/logic/fileHandler.py index 0ec43d86f..497241908 100644 --- a/app/logic/fileHandler.py +++ b/app/logic/fileHandler.py @@ -51,7 +51,7 @@ def getFileFullPath(self, newfilename=''): return filePath - def saveFiles(self, saveOriginalFile=None): + def saveFiles(self, saveOriginalFile): """ saveOriginalFile """ diff --git a/app/logic/minor.py b/app/logic/minor.py index 7bcd5bb8c..0727cbd96 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -28,35 +28,27 @@ def createSummerExperience(username, formData): Given the username of the student and the formData which includes all of the SummerExperience information, create a new SummerExperience object. """ - try: - user = User.get(User.username == username) - contentAreas = ', '.join(formData.getlist('contentArea')) # Combine multiple content areas - formData = dict(formData) - formData.pop("contentArea") - return CCEMinorProposal.create( - student=user, - proposalType = 'Summer Experience', - contentAreas = contentAreas, - createdBy = g.current_user, - **formData, - ) - except Exception as e: - print(f"Error saving summer experience: {e}") - raise e + user = User.get(User.username == username) + contentAreas = ', '.join(formData.getlist('contentArea')) # Combine multiple content areas + formData = dict(formData) + formData.pop("contentArea") + return CCEMinorProposal.create( + student=user, + proposalType = 'Summer Experience', + contentAreas = contentAreas, + createdBy = g.current_user, + **formData, + ) def updateSummerExperience(proposalID, formData): """ Given the username of the student and the formData which includes all of the SummerExperience information, create a new SummerExperience object. """ - try: - contentAreas = ', '.join(formData.getlist('contentArea')) # Combine multiple content areas - formData = dict(formData) - formData.pop("contentArea") - CCEMinorProposal.update(contentAreas=contentAreas, **formData).where(CCEMinorProposal.id == proposalID).execute() - except Exception as e: - print(f"Error saving summer experience: {e}") - raise e + contentAreas = ', '.join(formData.getlist('contentArea')) # Combine multiple content areas + formData = dict(formData) + formData.pop("contentArea") + CCEMinorProposal.update(contentAreas=contentAreas, **formData).where(CCEMinorProposal.id == proposalID).execute() def getCCEMinorProposals(username): proposalList = [] @@ -361,7 +353,6 @@ def createOtherEngagement(username, request): addFile = FileHandler(getFilesFromRequest(request), proposalId=createdProposal.id) addFile.saveFiles(saveOriginalFile=proposalObject) - def updateOtherEngagementRequest(proposalID, request): """ Update an existing CCEMinorProposal entry based off of the form data From 91908761221e3071bba38ffd12d5bb7111f9c870 Mon Sep 17 00:00:00 2001 From: makindeo Date: Mon, 10 Nov 2025 16:30:42 -0500 Subject: [PATCH 064/407] Improved the functionality of saveFiles() function --- app/logic/events.py | 2 +- app/logic/fileHandler.py | 89 ++++++++++++++++------------------ app/logic/minor.py | 4 +- tests/code/test_fileHandler.py | 10 ++-- 4 files changed, 51 insertions(+), 54 deletions(-) diff --git a/app/logic/events.py b/app/logic/events.py index 527355b69..634deed20 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -173,7 +173,7 @@ def attemptSaveEvent(eventData, attachmentFiles = None, renewedEvent = False): if attachmentFiles: for event in events: addFile = FileHandler(attachmentFiles, eventId=event.id) - addFile.saveFiles(saveOriginalFile=events[0]) + addFile.saveFiles(parentEvent=events[0]) return events, "" diff --git a/app/logic/fileHandler.py b/app/logic/fileHandler.py index 497241908..db3876339 100644 --- a/app/logic/fileHandler.py +++ b/app/logic/fileHandler.py @@ -24,7 +24,7 @@ def __init__(self, files=None, courseId=None, eventId=None, programId=None, prop elif programId: self.path = os.path.join(self.path, app.config['files']['program_attachment_path']) elif proposalId: - self.path = os.path.join(self.path, app.config['files']['proposal_attachment_path']) + self.path = os.path.join(self.path, app.config['files']['proposal_attachment_path'], str(proposalId)) def makeDirectory(self): try: @@ -51,59 +51,56 @@ def getFileFullPath(self, newfilename=''): return filePath - def saveFiles(self, saveOriginalFile): + def saveFiles(self, parentEvent=None): """ - saveOriginalFile + Saves attachments for different types and creates DB record for stored attachment """ - try: - for file in self.files: - saveFileToFilesystem = None + for file in self.files: + saveFileToFilesystem = None - if self.eventId: - attachmentName = str(saveOriginalFile.id) + "/" + file.filename - isFileInEvent = AttachmentUpload.select().where(AttachmentUpload.event_id == self.eventId, - AttachmentUpload.fileName == attachmentName).exists() - if not isFileInEvent: - AttachmentUpload.create(event=self.eventId, fileName=attachmentName) - if saveOriginalFile and saveOriginalFile.id == self.eventId: - saveFileToFilesystem = attachmentName - elif self.courseId: - isFileInCourse = AttachmentUpload.select().where(AttachmentUpload.course == self.courseId, AttachmentUpload.fileName == file.filename).exists() - if not isFileInCourse: - AttachmentUpload.create(course=self.courseId, fileName=file.filename) - saveFileToFilesystem = file.filename - elif self.programId: - - # remove the existing file - deleteFileObject = AttachmentUpload.get_or_none(program=self.programId) - if deleteFileObject: - self.deleteFile(deleteFileObject.id) - - # add the new file - fileType = file.filename.split('.')[-1] - fileName = f"{self.programId}.{fileType}" - AttachmentUpload.create(program=self.programId, fileName=fileName) - currentProgramID = fileName - saveFileToFilesystem = currentProgramID - - elif self.proposalId: - attachmentName = str(saveOriginalFile.id) + "/" + file.filename - isFileInProposal = AttachmentUpload.select().where(AttachmentUpload.proposal == self.proposalId, - AttachmentUpload.fileName == attachmentName).exists() - if not isFileInProposal: - # add the new file - AttachmentUpload.create(proposal=self.proposalId, fileName=attachmentName) + if self.eventId: + attachmentName = str(parentEvent.id) + "/" + file.filename + isFileInEvent = AttachmentUpload.select().where(AttachmentUpload.event_id == self.eventId, + AttachmentUpload.fileName == attachmentName).exists() + if not isFileInEvent: + AttachmentUpload.create(event=self.eventId, fileName=attachmentName) + if parentEvent and parentEvent.id == self.eventId: saveFileToFilesystem = attachmentName - else: + elif self.courseId or self.proposalId: + recordId = self.courseId if self.courseId else self.proposalId + fieldName = 'course' if self.courseId else 'proposal' + + fileExists = AttachmentUpload.select().where( + getattr(AttachmentUpload, fieldName) == recordId, + AttachmentUpload.fileName == file.filename + ).exists() + + if not fileExists: + create_data = {fieldName: recordId, 'fileName': file.filename} + AttachmentUpload.create(**create_data) saveFileToFilesystem = file.filename - if saveFileToFilesystem: - self.makeDirectory() - file.save(self.getFileFullPath(newfilename=saveFileToFilesystem)) + elif self.programId: + + # remove the existing file + deleteFileObject = AttachmentUpload.get_or_none(program=self.programId) + if deleteFileObject: + self.deleteFile(deleteFileObject.id) + + # add the new file + fileType = file.filename.split('.')[-1] + fileName = f"{self.programId}.{fileType}" + AttachmentUpload.create(program=self.programId, fileName=fileName) + currentProgramID = fileName + saveFileToFilesystem = currentProgramID + + else: + saveFileToFilesystem = file.filename - except AttributeError as e: - print(e) + if saveFileToFilesystem: + self.makeDirectory() + file.save(self.getFileFullPath(newfilename=saveFileToFilesystem)) def retrievePath(self, files): pathDict = {} diff --git a/app/logic/minor.py b/app/logic/minor.py index 0727cbd96..bcc1ba18c 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -351,7 +351,7 @@ def createOtherEngagement(username, request): attachment = request.files.get("attachmentObject") if attachment: addFile = FileHandler(getFilesFromRequest(request), proposalId=createdProposal.id) - addFile.saveFiles(saveOriginalFile=proposalObject) + addFile.saveFiles(parentEvent=proposalObject) def updateOtherEngagementRequest(proposalID, request): """ @@ -365,7 +365,7 @@ def updateOtherEngagementRequest(proposalID, request): deleteFile = FileHandler(proposalId=proposalID) deleteFile.deleteFile(existingAttachment.id) addFile = FileHandler(getFilesFromRequest(request), proposalId=proposalID) - addFile.saveFiles(saveOriginalFile=proposalObject) + addFile.saveFiles(parentEvent=proposalObject) CCEMinorProposal.update(**request.form).where(CCEMinorProposal.id == proposalID).execute() diff --git a/tests/code/test_fileHandler.py b/tests/code/test_fileHandler.py index 8b624cf5d..46c796fe8 100644 --- a/tests/code/test_fileHandler.py +++ b/tests/code/test_fileHandler.py @@ -62,16 +62,16 @@ def test_makingdirectory(): def test_saveFiles(): with mainDB.atomic() as transaction: # test event - handledEventFile.saveFiles(saveOriginalFile = Event.get_by_id(15)) + handledEventFile.saveFiles(parentEvent = Event.get_by_id(15)) assert AttachmentUpload.select().where(AttachmentUpload.fileName == '15/eventfile.pdf').exists() # test saving 2nd event in a hypothetical recurring series - handledEventFileRecurring.saveFiles(saveOriginalFile = Event.get_by_id(15)) + handledEventFileRecurring.saveFiles(parentEvent = Event.get_by_id(15)) assert AttachmentUpload.select().where(AttachmentUpload.event_id == 16, AttachmentUpload.fileName == '15/eventfile.pdf').exists() assert 1 == AttachmentUpload.select().where(AttachmentUpload.event_id == 16, AttachmentUpload.fileName == '15/eventfile.pdf').count() - handledEventFileRecurring.saveFiles(saveOriginalFile = Event.get_by_id(15)) + handledEventFileRecurring.saveFiles(parentEvent = Event.get_by_id(15)) assert 1 == AttachmentUpload.select().where(AttachmentUpload.event_id == 16, AttachmentUpload.fileName == '15/eventfile.pdf').count() # test course @@ -122,10 +122,10 @@ def test_retrievePath(): def test_deleteFile(): with mainDB.atomic() as transaction: # creates file in event file directory for deletion - handledEventFile.saveFiles(saveOriginalFile = Event.get_by_id(15)) + handledEventFile.saveFiles(parentEvent = Event.get_by_id(15)) # creates a second file to simulate recurring events - handledEventFileRecurring.saveFiles(saveOriginalFile = Event.get_by_id(15)) + handledEventFileRecurring.saveFiles(parentEvent = Event.get_by_id(15)) # creates a course file for deletion handledCourseFile.saveFiles() From b1acdb1291e723f80740ef65e36ac8577d8e416a Mon Sep 17 00:00:00 2001 From: makindeo Date: Tue, 11 Nov 2025 09:08:35 -0500 Subject: [PATCH 065/407] Fixed tests from saveFiles() improvement --- app/controllers/minor/routes.py | 2 +- tests/code/test_minor.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index a7708efca..bd9618fe3 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -82,7 +82,7 @@ def editOrViewProposal(proposalID: int): if attachmentObject: fileHandler = FileHandler(proposalId=proposalID) attachmentFilePath = fileHandler.getFileFullPath(attachmentObject.fileName).lstrip("app/") # we need to remove app/ from the url because it prevents it from displaying - attachmentFileName = attachmentObject.fileName.split("/", 1)[1] # get the name without our prepended id + attachmentFileName = attachmentObject.fileName if request.method == "GET": selectedTerm = Term.get_by_id(proposal.term) diff --git a/tests/code/test_minor.py b/tests/code/test_minor.py index 160ade140..9b3824e6e 100644 --- a/tests/code/test_minor.py +++ b/tests/code/test_minor.py @@ -680,7 +680,6 @@ def test_removeProposal(testProposal, testUser): testFileName = "proposal.pdf" testProposalId = testProposalObject.id - testFullFileName = f"{testProposalId}/{testFileName}" assert list(CCEMinorProposal.select().where(CCEMinorProposal.id == testProposalId)) == [testOtherEngagement] @@ -693,8 +692,8 @@ def test_removeProposal(testProposal, testUser): handledProposalFile.saveFiles(testProposalObject) try: - assert AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).exists() - assert 1 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).count() + assert AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFileName).exists() + assert 1 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFileName).count() with app.app_context(): g.current_user = testUser.username @@ -702,15 +701,15 @@ def test_removeProposal(testProposal, testUser): assert list(CCEMinorProposal.select().where(CCEMinorProposal.id == testProposalId)) == [] - assert not AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).exists() - assert 0 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFullFileName).count() + assert not AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFileName).exists() + assert 0 == AttachmentUpload.select().where(AttachmentUpload.proposal_id == testProposalId, AttachmentUpload.fileName == testFileName).count() except Exception as e: raise e finally: fileExists = AttachmentUpload.get_or_none(proposal_id = testProposalId) - fullFilePath = handledProposalFile.getFileFullPath(testFullFileName) + fullFilePath = handledProposalFile.getFileFullPath(testFileName) if fileExists: os.remove(fullFilePath) From 6be8d1580cb0518d935ab198e2d3392d50b5acfb Mon Sep 17 00:00:00 2001 From: makindeo Date: Tue, 11 Nov 2025 10:16:12 -0500 Subject: [PATCH 066/407] Fixed the bug where custom hours and custom experience description were not showing up once saved. --- app/logic/minor.py | 1 + app/static/js/minorProfilePage.js | 16 +++++++++------- app/templates/minor/summerExperience.html | 5 ++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/logic/minor.py b/app/logic/minor.py index bcc1ba18c..c0101a139 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -48,6 +48,7 @@ def updateSummerExperience(proposalID, formData): contentAreas = ', '.join(formData.getlist('contentArea')) # Combine multiple content areas formData = dict(formData) formData.pop("contentArea") + formData.pop("experienceHoursOver300") CCEMinorProposal.update(contentAreas=contentAreas, **formData).where(CCEMinorProposal.id == proposalID).execute() def getCCEMinorProposals(username): diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index 2c96ca286..ff315d4df 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -89,7 +89,8 @@ $(document).ready(function() { // ************** SUMMER EXPERIENCE ************** // $('#hoursBelow300Container').hide() - $('#otherExperienceDescription').hide() + toggleUnder300HoursTextarea() + toggleOtherExperienceTextarea() $("input[name='experienceHoursOver300']").on("change", function() { toggleUnder300HoursTextarea(); @@ -219,14 +220,15 @@ function toggleEngagementCredit(isChecked, engagementData, checkbox){ } function toggleUnder300HoursTextarea() { - var yesRadio = $('#yes300hours'); + var noRadio = $('#no300hours'); var conditionalTextBox = $('#hoursBelow300Container'); - if (yesRadio.is(':checked')) { - conditionalTextBox.hide() - $('#totalHours').val(300) + if (noRadio.is(':checked')) { + conditionalTextBox.show(); } else { - conditionalTextBox.show() - $('#totalHours').val('') + conditionalTextBox.hide(); + if ($('#yes300hours').is(':checked')) { + $('#totalHours').val(300); + } } } diff --git a/app/templates/minor/summerExperience.html b/app/templates/minor/summerExperience.html index 8a03307c0..3584d3003 100644 --- a/app/templates/minor/summerExperience.html +++ b/app/templates/minor/summerExperience.html @@ -102,10 +102,12 @@

Experience Information

{% if viewing %} disabled {% endif %} {% if proposal and proposal.experienceType == "Other" %} checked {% endif %}/>
+ {% if viewing and proposal.experienceDescription or not viewing %} + {% endif %}
@@ -143,13 +145,14 @@

Experience Information



From 35a50a8fe32f6fe78b235011a662b8cdf965a79b Mon Sep 17 00:00:00 2001 From: makindeo Date: Tue, 11 Nov 2025 10:27:51 -0500 Subject: [PATCH 067/407] Removed hanging logic for creating unnecessary /static dir --- app/logic/fileHandler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/logic/fileHandler.py b/app/logic/fileHandler.py index db3876339..12c2d77a1 100644 --- a/app/logic/fileHandler.py +++ b/app/logic/fileHandler.py @@ -31,8 +31,6 @@ def makeDirectory(self): extraDir = "" if self.eventId: extraDir = str(self.eventId) - if self.proposalId: - extraDir = str(self.proposalId) os.makedirs(os.path.join(self.path, extraDir)) except OSError as e: From 77811c4ffb37bcaab54a38a663cfe42cdaaf6a84 Mon Sep 17 00:00:00 2001 From: makindeo Date: Thu, 13 Nov 2025 11:45:48 -0500 Subject: [PATCH 068/407] Fixed merge bugs and re-added withdrawProposal() function --- app/static/js/cceMinorProposalManagement.js | 18 ++++++++++++++++++ app/static/js/minorProfilePage.js | 3 --- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/static/js/cceMinorProposalManagement.js b/app/static/js/cceMinorProposalManagement.js index 5ece2eabe..da8e58630 100644 --- a/app/static/js/cceMinorProposalManagement.js +++ b/app/static/js/cceMinorProposalManagement.js @@ -23,4 +23,22 @@ function changeAction(action){ function resetAllSelections() { $('.form-select').val('---'); } + +function withdrawProposal(){ + // uses hidden label to withdraw course + let proposalID = $("#proposalID").val(); + let username = $("#username").val() + $.ajax({ + url: `/cceMinor/withdraw/${username}/${proposalID}`, + type: "POST", + success: function(s){ + window.location.href = `/profile/${username}/cceMinor?tab=manageProposals` + }, + error: function(request, status, error) { + console.log(status, error); + } + }) + resetAllSelections() +}; + window.changeAction = changeAction; diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index dbc17d3b6..19af8dccc 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -52,9 +52,6 @@ $(document).ready(function() { data: formData, processData: false, contentType: false, - data: formData, - processData: false, - contentType: false, success: function(response) { window.location.href = `/profile/${username}/cceMinor?tab=manageProposals` }, From 29ae54e7937701d3a968f050c9b5161c9a5c31cd Mon Sep 17 00:00:00 2001 From: makindeo Date: Sun, 16 Nov 2025 12:16:50 -0500 Subject: [PATCH 069/407] Wrote logic for marking proposals as complete. Need to update the CSS and write tests for the new functionality --- app/controllers/minor/routes.py | 13 ++++++ app/logic/minor.py | 6 +++ app/static/js/cceMinorProposalManagement.js | 41 +++++++++++-------- .../minor/cceMinorProposalManagement.html | 2 +- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index bd9618fe3..6f1d78cc1 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -9,6 +9,7 @@ from app.logic.fileHandler import FileHandler from app.logic.utils import selectSurroundingTerms, getFilesFromRequest from app.logic.minor import ( + changeProposalStatus, createOtherEngagement, updateOtherEngagementRequest, setCommunityEngagementForUser, @@ -152,6 +153,18 @@ def withdrawProposal(username, proposalID): flash("Withdrawal Unsuccessful", 'warning') return "" +@minor_bp.route('/cceMinor/complete//', methods = ['POST']) +def completeProposal(username, proposalID): + try: + if g.current_user.isAdmin or g.current_user.isFaculty or g.current_user == username: + changeProposalStatus(proposalID, "Completed") + flash("Experience successfully completed", 'success') + else: + flash("Unauthorized to perform this action", 'warning') + except Exception as e: + print(e) + flash("Proposal status could not be changed", 'warning') + return "" @minor_bp.route('/cceMinor/getMinorSpreadsheet', methods=['GET']) def returnMinorSpreadsheet(): diff --git a/app/logic/minor.py b/app/logic/minor.py index 6fe862268..b27c90b86 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -436,3 +436,9 @@ def removeProposal(proposalID) -> None: proposalFileHandler.deleteFile(proposalAttachment.id) CCEMinorProposal.delete().where(CCEMinorProposal.id == proposalID).execute() + +def changeProposalStatus(proposalID, newStatus) -> None: + """ + Changes the status of a proposal. + """ + CCEMinorProposal.update(status=newStatus).where(CCEMinorProposal.id == int(proposalID)) diff --git a/app/static/js/cceMinorProposalManagement.js b/app/static/js/cceMinorProposalManagement.js index da8e58630..ed8d7dfa6 100644 --- a/app/static/js/cceMinorProposalManagement.js +++ b/app/static/js/cceMinorProposalManagement.js @@ -1,6 +1,8 @@ $(document).ready(function() { - $("#withdrawBtn").on("click", withdrawProposal); -}) + $("#withdrawBtn").on("click", function(){ + updateProposalStatus('withdraw') + }) +}); function changeAction(action){ let proposalID = action.id; let proposalType = $(action).data('type') @@ -8,15 +10,16 @@ function changeAction(action){ // decides what to do based on selection if (proposalAction == "Edit"){ location = `/cceMinor/edit${proposalType.replace(/\s+/g, '')}/` + proposalID; - } - if (proposalAction == "View"){ + } else if (proposalAction == "View"){ location = `/cceMinor/view${proposalType.replace(/\s+/g, '')}/` + proposalID; - } - if (proposalAction == "Withdraw"){ + } else if (proposalAction == "Withdraw"){ $('#proposalID').val(proposalID); $('#withdrawModal').modal('show'); - - } + } else if (proposalAction == "Completed"){ + $('#proposalID').val(proposalID); + updateProposalStatus('complete') + console.log('kkkk') + } resetAllSelections() } @@ -24,21 +27,23 @@ function resetAllSelections() { $('.form-select').val('---'); } -function withdrawProposal(){ - // uses hidden label to withdraw course +function updateProposalStatus(action){ + // for withdrawing proposals or marking them as complete let proposalID = $("#proposalID").val(); - let username = $("#username").val() + let username = $("#username").val(); + $.ajax({ - url: `/cceMinor/withdraw/${username}/${proposalID}`, + url: `/cceMinor/${action}/${username}/${proposalID}`, type: "POST", - success: function(s){ - window.location.href = `/profile/${username}/cceMinor?tab=manageProposals` - }, + success: function(res){ + window.location.href = `/profile/${username}/cceMinor?tab=manageProposals`; + }, error: function(request, status, error) { console.log(status, error); } - }) - resetAllSelections() -}; + }); + + resetAllSelections(); +} window.changeAction = changeAction; diff --git a/app/templates/minor/cceMinorProposalManagement.html b/app/templates/minor/cceMinorProposalManagement.html index 3e7df3385..8e66bc0fa 100644 --- a/app/templates/minor/cceMinorProposalManagement.html +++ b/app/templates/minor/cceMinorProposalManagement.html @@ -25,7 +25,7 @@ {% if g.current_user.isCeltsAdmin == True %} - + {% endif %} From 3cb3d355194c61f0eac5538adc0ab09740a20bd3 Mon Sep 17 00:00:00 2001 From: makindeo Date: Tue, 18 Nov 2025 11:11:30 -0500 Subject: [PATCH 070/407] Corrected the update proposal functionality and wrote tests for new functionality implemented --- app/logic/minor.py | 2 +- app/static/js/cceMinorProposalManagement.js | 1 - .../minor/cceMinorProposalManagement.html | 2 +- tests/code/test_minor.py | 31 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/logic/minor.py b/app/logic/minor.py index b27c90b86..18ef23b71 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -441,4 +441,4 @@ def changeProposalStatus(proposalID, newStatus) -> None: """ Changes the status of a proposal. """ - CCEMinorProposal.update(status=newStatus).where(CCEMinorProposal.id == int(proposalID)) + CCEMinorProposal.update(status=newStatus).where(CCEMinorProposal.id == int(proposalID)).execute() diff --git a/app/static/js/cceMinorProposalManagement.js b/app/static/js/cceMinorProposalManagement.js index ed8d7dfa6..1ba397ef9 100644 --- a/app/static/js/cceMinorProposalManagement.js +++ b/app/static/js/cceMinorProposalManagement.js @@ -18,7 +18,6 @@ function changeAction(action){ } else if (proposalAction == "Completed"){ $('#proposalID').val(proposalID); updateProposalStatus('complete') - console.log('kkkk') } resetAllSelections() } diff --git a/app/templates/minor/cceMinorProposalManagement.html b/app/templates/minor/cceMinorProposalManagement.html index 8e66bc0fa..62fc8d875 100644 --- a/app/templates/minor/cceMinorProposalManagement.html +++ b/app/templates/minor/cceMinorProposalManagement.html @@ -22,8 +22,8 @@ From e4b6306d19a74e0ab3a737b94ad30614acf79682 Mon Sep 17 00:00:00 2001 From: makindeo Date: Wed, 19 Nov 2025 15:31:22 -0500 Subject: [PATCH 072/407] Implemeneted logic that prevents students from modifying a proposal once it's been approved by an admin --- app/controllers/minor/routes.py | 11 ++++++++++- app/templates/minor/cceMinorProposalManagement.html | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 6f1d78cc1..668891761 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -75,7 +75,15 @@ def editOrViewProposal(proposalID: int): proposal = CCEMinorProposal.get_by_id(int(proposalID)) if not (g.current_user.isAdmin or g.current_user.username == proposal.student): return abort(403) + + isApproved = proposal.status in ['Approved', 'Completed'] + editProposal = 'view' not in request.path + # if proposal is approved, only admins can edit, but not if the admin is the student + if isApproved and editProposal: + if g.current_user.username == proposal.student or not g.current_user.isAdmin: + return abort(403) + attachmentObject = AttachmentUpload.get_or_none(proposal=proposalID) attachmentFilePath = "" attachmentFileName = "" @@ -88,7 +96,8 @@ def editOrViewProposal(proposalID: int): if request.method == "GET": selectedTerm = Term.get_by_id(proposal.term) return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/summerExperience.html", - editable = 'view' not in request.path, + editable = editProposal, + isApproved = isApproved, selectedTerm = selectedTerm, contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [], selectableTerms = selectSurroundingTerms(g.current_term, summerOnly=False if 'OtherEngagement' else True), diff --git a/app/templates/minor/cceMinorProposalManagement.html b/app/templates/minor/cceMinorProposalManagement.html index 0efbf166d..d7290f849 100644 --- a/app/templates/minor/cceMinorProposalManagement.html +++ b/app/templates/minor/cceMinorProposalManagement.html @@ -22,7 +22,9 @@ + {% from 'macros/cceMinorStatusMacro.html' import minorStatusMacro %} - {{ minorStatusMacro(proposal['status']) }} + {{ minorStatusMacro(proposal.status) }} {% endfor %} diff --git a/tests/code/test_minor.py b/tests/code/test_minor.py index 11a1674fb..36150e737 100644 --- a/tests/code/test_minor.py +++ b/tests/code/test_minor.py @@ -231,9 +231,9 @@ def test_getCCEMinorProposals(testUser, testProposal): summerExperienceCount = 0 otherExperienceCount = 0 for experience in getCCEMinorProposals(testUser.username): - if experience["type"] == "Summer Experience": + if experience.proposalType == "Summer Experience": summerExperienceCount+=1 - elif experience["type"] == "Other Engagement": + elif experience.proposalType == "Other Engagement": otherExperienceCount+=1 else: raise AssertionError From c5433d532098e2169bf31481705d0fd16dec4944 Mon Sep 17 00:00:00 2001 From: makindeo Date: Thu, 20 Nov 2025 12:54:42 -0500 Subject: [PATCH 074/407] Added flash message to inform students that proposals can only be edited by admins after submission. --- app/controllers/minor/routes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 668891761..61c639a41 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -29,8 +29,6 @@ def viewCceMinor(username): """ Load minor management page with community engagements and summer experience """ - if not (g.current_user.isAdmin): - return abort(403) sustainedEngagementByTerm = getCommunityEngagementByTerm(username) @@ -73,7 +71,7 @@ def createOtherEngagementRequest(username): @minor_bp.route('/cceMinor/editSummerExperience/', methods=['GET', 'POST']) def editOrViewProposal(proposalID: int): proposal = CCEMinorProposal.get_by_id(int(proposalID)) - if not (g.current_user.isAdmin or g.current_user.username == proposal.student): + if not (g.current_user.isAdmin or g.current_user.username == proposal.student.username): return abort(403) isApproved = proposal.status in ['Approved', 'Completed'] @@ -95,6 +93,7 @@ def editOrViewProposal(proposalID: int): if request.method == "GET": selectedTerm = Term.get_by_id(proposal.term) + flash("Once approved, a proposal can only be edited by an admin.", 'warning') return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/summerExperience.html", editable = editProposal, isApproved = isApproved, From 5662f6934f87eae8b9c945d6e206b458aac2ce98 Mon Sep 17 00:00:00 2001 From: makindeo Date: Thu, 20 Nov 2025 15:08:29 -0500 Subject: [PATCH 075/407] Added isApproved() property and added functionality to approve or unapprove a proposal conveniently --- app/controllers/minor/routes.py | 49 ++++++++++--------- app/models/cceMinorProposal.py | 4 ++ app/static/js/cceMinorProposalManagement.js | 34 +++++++------ .../minor/cceMinorProposalManagement.html | 11 ++++- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 61c639a41..130440bb5 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -74,11 +74,10 @@ def editOrViewProposal(proposalID: int): if not (g.current_user.isAdmin or g.current_user.username == proposal.student.username): return abort(403) - isApproved = proposal.status in ['Approved', 'Completed'] editProposal = 'view' not in request.path # if proposal is approved, only admins can edit, but not if the admin is the student - if isApproved and editProposal: + if proposal.isApproved and editProposal: if g.current_user.username == proposal.student or not g.current_user.isAdmin: return abort(403) @@ -96,7 +95,6 @@ def editOrViewProposal(proposalID: int): flash("Once approved, a proposal can only be edited by an admin.", 'warning') return render_template("minor/requestOtherEngagement.html" if 'OtherEngagement' in request.path else "minor/summerExperience.html", editable = editProposal, - isApproved = isApproved, selectedTerm = selectedTerm, contentAreas = proposal.contentAreas.split(", ") if proposal.contentAreas else [], selectableTerms = selectSurroundingTerms(g.current_term, summerOnly=False if 'OtherEngagement' else True), @@ -148,32 +146,35 @@ def getEngagementInformation(username, type, id, term): return information -@minor_bp.route('/cceMinor/withdraw//', methods = ['POST']) -def withdrawProposal(username, proposalID): +@minor_bp.route('/cceMinor///', methods=['POST']) +def updateProposal(action, username, proposalId): try: - if g.current_user.isAdmin or g.current_user.isFaculty or g.current_user == username: - removeProposal(proposalID) - flash("Experience successfully withdrawn", 'success') - else: - flash("Unauthorized to perform this action", 'warning') - except Exception as e: - print(e) - flash("Withdrawal Unsuccessful", 'warning') - return "" - -@minor_bp.route('/cceMinor/complete//', methods = ['POST']) -def completeProposal(username, proposalID): - try: - if g.current_user.isAdmin or g.current_user.isFaculty or g.current_user == username: - changeProposalStatus(proposalID, "Completed") - flash("Experience successfully completed", 'success') - else: - flash("Unauthorized to perform this action", 'warning') + if not (g.current_user.isAdmin or g.current_user.isFaculty or g.current_user.username == username): + flash("Unauthorized to perform this action", "warning") + return "" + + actionMap = { + "withdraw": ("Withdrawn", "Proposal successfully withdrawn"), + "complete": ("Completed", "Proposal successfully completed"), + "approve": ("Approved", "Proposal approved"), + "unapprove": ("Submitted", "Proposal unapproved"), + } + + if action not in actionMap: + flash("Invalid action", "warning") + return "" + + newStatus, message = actionMap[action] + changeProposalStatus(proposalId, newStatus) + flash(message, "success") + except Exception as e: print(e) - flash("Proposal status could not be changed", 'warning') + flash("Proposal status could not be changed", "warning") + return "" + @minor_bp.route('/cceMinor/getMinorSpreadsheet', methods=['GET']) def returnMinorSpreadsheet(): """ diff --git a/app/models/cceMinorProposal.py b/app/models/cceMinorProposal.py index e1081bd1c..2d0ae1763 100644 --- a/app/models/cceMinorProposal.py +++ b/app/models/cceMinorProposal.py @@ -31,3 +31,7 @@ def isOver300Hours(self): if not int(self.totalHours) or (int(self.totalHours) and int(self.totalHours) >= 300): return True return False + + @property + def isApproved(self): + return self.status in ['Approved', 'Completed'] diff --git a/app/static/js/cceMinorProposalManagement.js b/app/static/js/cceMinorProposalManagement.js index 1ba397ef9..ae1ca3278 100644 --- a/app/static/js/cceMinorProposalManagement.js +++ b/app/static/js/cceMinorProposalManagement.js @@ -3,24 +3,26 @@ $(document).ready(function() { updateProposalStatus('withdraw') }) }); -function changeAction(action){ - let proposalID = action.id; - let proposalType = $(action).data('type') - let proposalAction = action.value; - // decides what to do based on selection - if (proposalAction == "Edit"){ - location = `/cceMinor/edit${proposalType.replace(/\s+/g, '')}/` + proposalID; - } else if (proposalAction == "View"){ - location = `/cceMinor/view${proposalType.replace(/\s+/g, '')}/` + proposalID; - } else if (proposalAction == "Withdraw"){ - $('#proposalID').val(proposalID); + +function changeAction(element) { + const proposalId = element.id; + const proposalType = $(element).data('type'); + const proposalAction = element.value; + $('#proposalID').val(proposalId); + + if (proposalAction === "Edit") { + location = `/cceMinor/edit${proposalType.replace(/\s+/g, '')}/${proposalId}`; + } else if (proposalAction === "View") { + location = `/cceMinor/view${proposalType.replace(/\s+/g, '')}/${proposalId}`; + } else if (proposalAction === "withdraw") { $('#withdrawModal').modal('show'); - } else if (proposalAction == "Completed"){ - $('#proposalID').val(proposalID); - updateProposalStatus('complete') + } else { + updateProposalStatus(proposalAction.toLowerCase()); } - resetAllSelections() - } + + resetAllSelections(); +} + function resetAllSelections() { $('.form-select').val('---'); diff --git a/app/templates/minor/cceMinorProposalManagement.html b/app/templates/minor/cceMinorProposalManagement.html index ce8917e05..e8a457e65 100644 --- a/app/templates/minor/cceMinorProposalManagement.html +++ b/app/templates/minor/cceMinorProposalManagement.html @@ -22,12 +22,19 @@ From c1d10b782077626cd136283b24a0e089d1de959f Mon Sep 17 00:00:00 2001 From: makindeo Date: Fri, 21 Nov 2025 18:37:29 -0500 Subject: [PATCH 076/407] Fixed the bug with summer experience type not showing up --- app/templates/minor/summerExperience.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/minor/summerExperience.html b/app/templates/minor/summerExperience.html index 3584d3003..b2117f757 100644 --- a/app/templates/minor/summerExperience.html +++ b/app/templates/minor/summerExperience.html @@ -92,7 +92,7 @@

Experience Information


+ {% if proposal and proposal.experienceType == "EPG Summer Internship" %} checked {% endif %}/>
Date: Tue, 25 Nov 2025 15:45:14 -0500 Subject: [PATCH 077/407] Fixing small JS bugs scattered across files. --- app/static/js/minorProfilePage.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index 19af8dccc..a39bbc83a 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -3,8 +3,6 @@ import { validateEmail } from "./emailValidation.mjs"; $(document).ready(function() { $("#supervisorEmail").on('input', validateEmail); - handleFileSelection("supervisorAttachment", true) - $('input.phone-input').inputmask('(999)-999-9999') $('input.phone-input').on('input', function(){ let matches = $(this).val().match(/\d/g); @@ -19,18 +17,21 @@ $(document).ready(function() { } }) - handleFileSelection("supervisorAttachment", true, "preexistingFiles") + handleFileSelection("supervisorAttachment", true) function saveProposalData(status) { + let isValid = true; $("#proposalForm").find('[required]').each(function() { if (!$(this).val()) { this.setCustomValidity('Please fill out this field.') this.reportValidity() - return + isValid = false; + return false; } else { this.setCustomValidity('') } }); + if (!isValid) return; if ($('#proposalExperienceType').val() == "Other Engagement") { var fileRowCount = $('#supervisorAttachmentContainer').children().length; if (fileRowCount == 0) { From 9de83573ff841fec93a5f66441f017d756777ecb Mon Sep 17 00:00:00 2001 From: Oluwagbayi James Makinde <122561188+ojmakinde@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:14:16 -0500 Subject: [PATCH 078/407] Added input check for checkboxes and radios in proposal creation --- app/static/js/minorProfilePage.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index a39bbc83a..70c86c3d1 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -22,6 +22,17 @@ $(document).ready(function() { function saveProposalData(status) { let isValid = true; $("#proposalForm").find('[required]').each(function() { + if ($(this).is(':checkbox') || $(this).is(':radio')) { + let name = $(this).attr('name'); + if ($(`input[name="${name}"]:checked`).length === 0) { + this.setCustomValidity('Please select at least one option.') + this.reportValidity() + isValid = false; + return false; + } else { + this.setCustomValidity('') + } + } else {} if (!$(this).val()) { this.setCustomValidity('Please fill out this field.') this.reportValidity() From 8a1d591d89faf13f01ef5ecfa2f4a20f2d625e16 Mon Sep 17 00:00:00 2001 From: Oluwagbayi James Makinde <122561188+ojmakinde@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:39:45 -0500 Subject: [PATCH 079/407] Fixed the withdraw modal --- app/controllers/minor/routes.py | 6 +++++- app/templates/minor/cceMinorProposalManagement.html | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/minor/routes.py b/app/controllers/minor/routes.py index 130440bb5..4ab3b330d 100644 --- a/app/controllers/minor/routes.py +++ b/app/controllers/minor/routes.py @@ -165,7 +165,11 @@ def updateProposal(action, username, proposalId): return "" newStatus, message = actionMap[action] - changeProposalStatus(proposalId, newStatus) + + if action == "withdraw": + removeProposal(proposalId) + else: + changeProposalStatus(proposalId, newStatus) flash(message, "success") except Exception as e: diff --git a/app/templates/minor/cceMinorProposalManagement.html b/app/templates/minor/cceMinorProposalManagement.html index e8a457e65..524c316da 100644 --- a/app/templates/minor/cceMinorProposalManagement.html +++ b/app/templates/minor/cceMinorProposalManagement.html @@ -32,7 +32,7 @@ {% endif %} {% endif %} - + {% if g.current_user.isCeltsAdmin == True %} {% endif %} From 8dcbc773aebdd0e112ce0727eb2217f564ac0865 Mon Sep 17 00:00:00 2001 From: Oluwagbayi James Makinde <122561188+ojmakinde@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:24:38 -0500 Subject: [PATCH 080/407] Fixed form validation and re-implemented native validation, removing need for extra implementation --- app/static/js/minorProfilePage.js | 66 +++++++------------ .../minor/requestOtherEngagement.html | 11 ++-- 2 files changed, 26 insertions(+), 51 deletions(-) diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index 70c86c3d1..733e1fc88 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -19,45 +19,32 @@ $(document).ready(function() { handleFileSelection("supervisorAttachment", true) - function saveProposalData(status) { - let isValid = true; - $("#proposalForm").find('[required]').each(function() { - if ($(this).is(':checkbox') || $(this).is(':radio')) { - let name = $(this).attr('name'); - if ($(`input[name="${name}"]:checked`).length === 0) { - this.setCustomValidity('Please select at least one option.') - this.reportValidity() - isValid = false; - return false; - } else { - this.setCustomValidity('') - } - } else {} - if (!$(this).val()) { - this.setCustomValidity('Please fill out this field.') - this.reportValidity() - isValid = false; - return false; - } else { - this.setCustomValidity('') - } - }); - if (!isValid) return; + $('.submit-proposal').on('click', function(e) { + e.preventDefault(); + let status = $(this).data('status'); + $('#statusField').val(status); + + if (!$('#proposalForm')[0].checkValidity()) { + $('#proposalForm')[0].reportValidity(); + return; + } + + // file validation if ($('#proposalExperienceType').val() == "Other Engagement") { var fileRowCount = $('#supervisorAttachmentContainer').children().length; if (fileRowCount == 0) { $('#supervisorAttachment')[0].setCustomValidity('Please upload a file.'); - $('#supervisorAttachment')[0].reportValidity() - return + $('#supervisorAttachment')[0].reportValidity(); + return; } else { - $('#supervisorAttachment')[0].setCustomValidity(''); + $('#supervisorAttachment')[0].setCustomValidity(''); } } - var formData = new FormData($("#proposalForm")[0]); - formData.append("status", status); - - var actionURL = $("#proposalForm").attr('action') - let username = $("#username").val() + + var formData = new FormData($('#proposalForm')[0]); + var actionURL = $('#proposalForm').attr('action'); + let username = $("#username").val(); + $.ajax({ url: actionURL, type: 'POST', @@ -65,23 +52,14 @@ $(document).ready(function() { processData: false, contentType: false, success: function(response) { - window.location.href = `/profile/${username}/cceMinor?tab=manageProposals` + window.location.href = `/profile/${username}/cceMinor?tab=manageProposals`; }, error: function(xhr, status, error) { console.error('Error:', error); } }); - } - - $('#saveAsDraftButton').on('click', function() { - saveProposalData("Draft") - }) - $('#submitButton').on('click', function() { - saveProposalData("Submitted") - }) - $('#saveAndApproveButton').on('click', function() { - saveProposalData("Approved") - }) + }); + $('#exitButton').on('click', function() { let username = $("#username").val() window.location.href = `/profile/${username}/cceMinor?tab=manageProposals` diff --git a/app/templates/minor/requestOtherEngagement.html b/app/templates/minor/requestOtherEngagement.html index 0b4184390..f00c2adb2 100644 --- a/app/templates/minor/requestOtherEngagement.html +++ b/app/templates/minor/requestOtherEngagement.html @@ -118,13 +118,10 @@

Experience Information

- {% if not viewing %} - - - {% if g.current_user.isCeltsAdmin and proposal %} - - {% endif %} - {% endif %} + + + +
From 58d0f487d0c670cd6728a42e057e70441b68b963 Mon Sep 17 00:00:00 2001 From: Rue Haile Date: Wed, 17 Dec 2025 16:45:48 -0500 Subject: [PATCH 081/407] Fixing the button for the otherEngagement page. --- app/templates/minor/requestOtherEngagement.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/minor/requestOtherEngagement.html b/app/templates/minor/requestOtherEngagement.html index f00c2adb2..7630ca02c 100644 --- a/app/templates/minor/requestOtherEngagement.html +++ b/app/templates/minor/requestOtherEngagement.html @@ -120,8 +120,8 @@

Experience Information

- - + +
From 31db0f20a03dc0269f8b15d8d0127d9e5318a65c Mon Sep 17 00:00:00 2001 From: bakobagassas Date: Wed, 17 Dec 2025 16:49:41 -0500 Subject: [PATCH 082/407] Same message + format as the other tabs --- app/templates/events/eventList.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/events/eventList.html b/app/templates/events/eventList.html index ff0dbf522..8e0a4f8a1 100644 --- a/app/templates/events/eventList.html +++ b/app/templates/events/eventList.html @@ -208,7 +208,7 @@

Events List for {{selectedTerm.description}}

{% endfor %} {% else %} -

There are no events that earn service for this term.

+ There are no upcoming events for this program {% endif %}
From e2a048df525a0cc3de6cf25201d130327f8e6677 Mon Sep 17 00:00:00 2001 From: bakobagassas Date: Wed, 17 Dec 2025 17:00:29 -0500 Subject: [PATCH 083/407] standardized display when there are no events in V.Opportunities --- app/templates/events/eventList.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/templates/events/eventList.html b/app/templates/events/eventList.html index 8e0a4f8a1..18a169c17 100644 --- a/app/templates/events/eventList.html +++ b/app/templates/events/eventList.html @@ -208,6 +208,20 @@

Events List for {{selectedTerm.description}}

{% endfor %}
{% else %} +
+ + + + + + + + + + + +
ProgramEvent NameDateTimeLocation
+
There are no upcoming events for this program {% endif %} From 3e1299188b6a4575cb8eef03a23ecacd31e46825 Mon Sep 17 00:00:00 2001 From: Rue Haile Date: Wed, 17 Dec 2025 17:09:10 -0500 Subject: [PATCH 084/407] file save error half fix --- app/static/js/minorProfilePage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index 733e1fc88..62a446acb 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -31,8 +31,8 @@ $(document).ready(function() { // file validation if ($('#proposalExperienceType').val() == "Other Engagement") { - var fileRowCount = $('#supervisorAttachmentContainer').children().length; - if (fileRowCount == 0) { + var fileRowCount = $('#supervisorAttachmentContainer')[0].files; + if (fileRowCount.length == 0) { $('#supervisorAttachment')[0].setCustomValidity('Please upload a file.'); $('#supervisorAttachment')[0].reportValidity(); return; From 66e4ec148ea8edf1f095ccd33f0c5fb0c96b09fb Mon Sep 17 00:00:00 2001 From: Rue Haile Date: Thu, 18 Dec 2025 19:12:30 -0500 Subject: [PATCH 085/407] Buttons are functional --- app/logic/minor.py | 57 +++++++++++++------ app/static/js/minorProfilePage.js | 14 +++-- .../minor/requestOtherEngagement.html | 1 - app/templates/minor/summerExperience.html | 7 ++- 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/app/logic/minor.py b/app/logic/minor.py index 58c3222d3..3da351cd1 100644 --- a/app/logic/minor.py +++ b/app/logic/minor.py @@ -5,7 +5,7 @@ from peewee import JOIN, fn, Case, DoesNotExist, SQL import xlsxwriter -from app import app +from app.models import mainDB from app.models.user import User from app.models.term import Term from app.models.event import Event @@ -341,21 +341,46 @@ def createOtherEngagement(username, request): addFile.saveFiles(parentEvent=proposalObject) def updateOtherEngagementRequest(proposalID, request): - """ - Update an existing CCEMinorProposal entry based off of the form data - """ - attachment = request.files.get("attachmentObject") - proposalObject = CCEMinorProposal.get_by_id(proposalID) - if attachment: - # delete the existing file to make space for the new one - existingAttachment = AttachmentUpload.get(proposal=proposalID) - deleteFile = FileHandler(proposalId=proposalID) - deleteFile.deleteFile(existingAttachment.id) - addFile = FileHandler(getFilesFromRequest(request), proposalId=proposalID) - addFile.saveFiles(parentEvent=proposalObject) - - CCEMinorProposal.update(**request.form).where(CCEMinorProposal.id == proposalID).execute() - + attachment = request.files.get("attachmentObject") + + with mainDB.atomic(): + # Get proposal safely + proposalObject = CCEMinorProposal.get_by_id(proposalID) + + # ---- HANDLE ATTACHMENT SAFELY ---- + if attachment: + existingAttachment = ( + AttachmentUpload + .select() + .where(AttachmentUpload.proposal == proposalID) + .first() + ) + + if existingAttachment: + deleteFile = FileHandler(proposalId=proposalID) + deleteFile.deleteFile(existingAttachment.id) + + addFile = FileHandler( + getFilesFromRequest(request), + proposalId=proposalID + ) + addFile.saveFiles(parentEvent=proposalObject) + + # ---- CLEAN FORM DATA ---- + update_data = dict(request.form) + + # remove fields that are not DB columns + update_data.pop("contentArea", None) + update_data.pop("attachmentObject", None) + + # ---- UPDATE PROPOSAL ---- + ( + CCEMinorProposal + .update(**update_data) + .where(CCEMinorProposal.id == proposalID) + .execute() + ) + def saveSummerExperience(username, summerExperience, currentUser): """ :param username: username of the student that the summer experience is for diff --git a/app/static/js/minorProfilePage.js b/app/static/js/minorProfilePage.js index 62a446acb..e16c55176 100644 --- a/app/static/js/minorProfilePage.js +++ b/app/static/js/minorProfilePage.js @@ -26,18 +26,20 @@ $(document).ready(function() { if (!$('#proposalForm')[0].checkValidity()) { $('#proposalForm')[0].reportValidity(); + $( '#proposalForm').submit() return; } // file validation - if ($('#proposalExperienceType').val() == "Other Engagement") { - var fileRowCount = $('#supervisorAttachmentContainer')[0].files; - if (fileRowCount.length == 0) { - $('#supervisorAttachment')[0].setCustomValidity('Please upload a file.'); - $('#supervisorAttachment')[0].reportValidity(); + if ($('#proposalExperienceType').val() === "Other Engagement") { + const fileInput = $('#supervisorAttachment')[0]; + const files = fileInput.files; + if (!files || files.length === 0) { + fileInput.setCustomValidity('Please upload a file.'); + fileInput.reportValidity(); return; } else { - $('#supervisorAttachment')[0].setCustomValidity(''); + fileInput.setCustomValidity(''); } } diff --git a/app/templates/minor/requestOtherEngagement.html b/app/templates/minor/requestOtherEngagement.html index 7630ca02c..6a80782bc 100644 --- a/app/templates/minor/requestOtherEngagement.html +++ b/app/templates/minor/requestOtherEngagement.html @@ -121,7 +121,6 @@

Experience Information

- diff --git a/app/templates/minor/summerExperience.html b/app/templates/minor/summerExperience.html index b2117f757..abdd5e7f6 100644 --- a/app/templates/minor/summerExperience.html +++ b/app/templates/minor/summerExperience.html @@ -20,6 +20,7 @@ {% set viewing = not editable and proposal %}
+
@@ -168,10 +169,10 @@

Experience Information

{% if not viewing %} - - + + {% if g.current_user.isCeltsAdmin and proposal %} - + {% endif %} {% endif %}
From 74ca5c9142dd27e422df06a2f008135e2eb046b4 Mon Sep 17 00:00:00 2001 From: Rue Haile Date: Fri, 19 Dec 2025 11:14:54 -0500 Subject: [PATCH 086/407] css fix --- app/templates/minor/requestOtherEngagement.html | 2 +- app/templates/minor/summerExperience.html | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/templates/minor/requestOtherEngagement.html b/app/templates/minor/requestOtherEngagement.html index 6a80782bc..0f8be9335 100644 --- a/app/templates/minor/requestOtherEngagement.html +++ b/app/templates/minor/requestOtherEngagement.html @@ -120,7 +120,7 @@

Experience Information

- +
diff --git a/app/templates/minor/summerExperience.html b/app/templates/minor/summerExperience.html index abdd5e7f6..e34b5f14b 100644 --- a/app/templates/minor/summerExperience.html +++ b/app/templates/minor/summerExperience.html @@ -170,10 +170,7 @@

Experience Information

{% if not viewing %} - - {% if g.current_user.isCeltsAdmin and proposal %} - - {% endif %} + {% endif %}
From 21e79efd83e80f667e45839e811bd2cbbad35f17 Mon Sep 17 00:00:00 2001 From: Besher Kitaz Date: Fri, 19 Dec 2025 20:32:14 -0500 Subject: [PATCH 087/407] Error Found. Now need to fix it! --- app/static/js/createEvents.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index b2f9314df..81e811ebc 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -75,6 +75,7 @@ function setViewForSingleOffering() { function setViewForSeries() { $(".startDatePicker").prop('required', false); + $(".startDatePicker").hide(); $("#multipleOfferingTableDiv").removeClass('d-none'); $("#eventLocation-main").show(); $("#inputEventLocation-main").prop('required', false); @@ -89,8 +90,8 @@ function displayNotification(message) { $('.invalidFeedback').text(message); $('.invalidFeedback').css('display', 'block'); $('.invalidFeedback').on('animationend', function () { - $('.invalidFeedback').css('display', 'none'); - $('#textNotifierPadding').removeClass('pt-5') + $('.invalidFeedback').css('display', 'none'); + $('#textNotifierPadding').removeClass('pt-5'); }); } @@ -205,11 +206,10 @@ $('#saveSeries').on('click', function(e) { enableLiveCustomValidityClearing([".multipleOfferingNameField"]) let eventOfferings = $('#multipleOfferingSlots .eventOffering'); let eventNameInputs = $('#multipleOfferingSlots .multipleOfferingNameField'); - let eventLocationInput = $('') + let eventLocationInputs = $('#multipleOfferingSlots .multipleOfferingLocationField'); let datePickerInputs = $('#multipleOfferingSlots .multipleOfferingDatePicker'); let startTimeInputs = $('#multipleOfferingSlots .multipleOfferingStartTime'); let endTimeInputs = $('#multipleOfferingSlots .multipleOfferingEndTime'); - let locationInputs = $('#multipleOfferingSlots .multipleOfferingLocationField'); let isRepeatingStatus = $("#checkIsRepeating").is(":checked"); let startDateInput = $("#repeatingEventsStartDate"); let endDateInput = $("#repeatingEventsEndDate"); @@ -238,6 +238,7 @@ $('#saveSeries').on('click', function(e) { } else { // Validate individual event offerings for non-repeating events + // Check event name fields eventNameInputs.each((index, eventNameInput) => { if (eventNameInput.value.trim() === '') { @@ -247,6 +248,18 @@ $('#saveSeries').on('click', function(e) { } else { $(eventNameInput)[0].setCustomValidity(""); } + }); + + + // Check location fields + eventLocationInputs.each((index, eventLocationInput) => { + if (eventLocationInput.value.trim() === '') { + hasErrors = true; + $(eventLocationInput)[0].setCustomValidity("Please enter an event location"); + $(eventLocationInput)[0].reportValidity(); + } else { + $(eventLocationInput)[0].setCustomValidity(""); + } }); // Check date picker fields @@ -326,7 +339,7 @@ function updateEventNameField() { if (!isSeries) { // if not weeekly, add them to a set to remove duplicates, then put them in a string to populate the field - $('#inputEventName').val('') + $('#inputEventName').val('') // ERROR IS HERE! FINALLY FOUND IT!! } else { // if weekly, take the name of the first item (which is the same for all) and take the word 'week' @@ -640,7 +653,7 @@ $("#cancelEvent").on('click', function (event) { setViewForSeries(); loadOfferingsToModal(); $('#modalSeries').modal('show'); - + // Disable single event name and location fields $('#inputEventName').prop('readonly', true); $('#inputEventLocation-main').prop('readonly', true); From b12d56b4e0b724e4af95778d01d09eedd573eaaa Mon Sep 17 00:00:00 2001 From: Besher Kitaz Date: Sun, 21 Dec 2025 18:50:04 -0500 Subject: [PATCH 088/407] Fixed the issue by preventing the name from being depopulated in the original name field --- app/static/js/createEvents.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/app/static/js/createEvents.js b/app/static/js/createEvents.js index 81e811ebc..012c28ba0 100644 --- a/app/static/js/createEvents.js +++ b/app/static/js/createEvents.js @@ -64,13 +64,14 @@ function calculateRepeatingEventFrequency() { function setViewForSingleOffering() { $(".startDatePicker").prop('required', true); + $(".startDatePicker").show(); $("#multipleOfferingTableDiv").addClass('d-none'); $("#eventLocation-main").hide(); $("#eventLocation-main").show(); $("#inputEventLocation-main").prop('required', true); $('#eventTime, #eventDate').show(); - $('#checkIsSeriesToggleContainer').removeClass('col-md-6') - $('#checkIsSeriesToggleContainer').addClass('col-md-12') + $('#checkIsSeriesToggleContainer').removeClass('col-md-12') + $('#checkIsSeriesToggleContainer').addClass('col-md-6') } function setViewForSeries() { @@ -117,12 +118,10 @@ function initializeFlatpickr(obj) { }); } -let eventSessionNum = 0; function createOfferingModalRow({ eventName = null, eventDate = null, startTime = null, endTime = null, eventLocation = null } = {}) { let clonedOffering = $("#multipleOfferingEvent").clone().removeClass('d-none').removeAttr("id"); - const baseName = $('#inputEventName').val(); - const fullName = baseName + ': session ' + (eventSessionNum + 1); - clonedOffering.find('.multipleOfferingNameField').val(fullName); + const Name = $('#inputEventName').val(); + clonedOffering.find('.multipleOfferingNameField').val(Name); // insert values for the newly created row @@ -132,7 +131,6 @@ function createOfferingModalRow({ eventName = null, eventDate = null, startTime if (endTime) { clonedOffering.find('.multipleOfferingEndTime').val(endTime) } if (eventLocation) { clonedOffering.find('.multipleOfferingLocationField').val(eventLocation) } - eventSessionNum++; $("#multipleOfferingSlots").append(clonedOffering); pendingmultipleEvents.push(clonedOffering); @@ -334,15 +332,8 @@ $('#saveSeries').on('click', function(e) { // Populate the Event Name field in the main page with the entered repeating events function updateEventNameField() { - let offerings = JSON.parse($("#seriesData").val()) let isSeries = $("#checkIsRepeating").is(":checked") - - if (!isSeries) { - // if not weeekly, add them to a set to remove duplicates, then put them in a string to populate the field - $('#inputEventName').val('') // ERROR IS HERE! FINALLY FOUND IT!! - } - else { - // if weekly, take the name of the first item (which is the same for all) and take the word 'week' + if (isSeries) { let offeringText = $("#repeatingEventsNamePicker").val() $('#inputEventName').val(offeringText) } From 6adf2ca7b0e7b941e28232d885e81a317db8a05d Mon Sep 17 00:00:00 2001 From: bakobagassas Date: Mon, 22 Dec 2025 12:41:32 -0500 Subject: [PATCH 089/407] new function for the past volunteer opportunities events --- app/logic/events.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/logic/events.py b/app/logic/events.py index db921d149..fd3c47422 100644 --- a/app/logic/events.py +++ b/app/logic/events.py @@ -277,6 +277,32 @@ def getUpcomingVolunteerOpportunitiesCount(term, currentTime): programCountDict[programCount.id] = programCount.eventCount return programCountDict +def getpastVolunteerOpportunitiesCount(term, currentTime): + """ + Return a count of all past events for each volunteer opportunities program. + """ + + pastCount = ( + Program + .select(Program.id, fn.COUNT(Event.id).alias("eventCount")) + .join(Event, on=(Program.id == Event.program_id)) + .where( + (Event.term == term) & + (Event.deletionDate.is_null(True)) & + (Event.isService == True) & + ((Event.isLaborOnly == False) | Event.isLaborOnly.is_null(True)) & + ((Event.startDate < currentTime) | + ((Event.startDate == currentTime) & (Event.timeStart <= currentTime))) & + (Event.isCanceled == False) + ) + .group_by(Program.id) + ) + + programCountDict = {} + for programCount in pastCount: + programCountDict[programCount.id] = programCount.eventCount + return programCountDict + def getTrainingEvents(term, user): """ The allTrainingsEvent query is designed to select and count eventId's after grouping them From 2f05e56ab88dc668cf4cb7944a883d54f0e22e08 Mon Sep 17 00:00:00 2001 From: bakobagassas Date: Mon, 22 Dec 2025 12:42:25 -0500 Subject: [PATCH 090/407] used the new function in routes and hmtl to display the number of past events we have for a program --- app/controllers/main/routes.py | 6 +++++- app/templates/events/eventList.html | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/controllers/main/routes.py b/app/controllers/main/routes.py index f9d17d1c4..2955f5cff 100644 --- a/app/controllers/main/routes.py +++ b/app/controllers/main/routes.py @@ -26,7 +26,7 @@ from app.models.courseInstructor import CourseInstructor from app.models.backgroundCheckType import BackgroundCheckType -from app.logic.events import getUpcomingEventsForUser, getParticipatedEventsForUser, getTrainingEvents, getEventRsvpCountsForTerm, getUpcomingVolunteerOpportunitiesCount, getVolunteerOpportunities, getBonnerEvents, getCeltsLabor, getEngagementEvents +from app.logic.events import getUpcomingEventsForUser, getParticipatedEventsForUser, getTrainingEvents, getEventRsvpCountsForTerm, getUpcomingVolunteerOpportunitiesCount, getVolunteerOpportunities, getBonnerEvents, getCeltsLabor, getEngagementEvents, getpastVolunteerOpportunitiesCount from app.logic.transcript import * from app.logic.loginManager import logout from app.logic.searchUsers import searchUsers @@ -92,6 +92,7 @@ def events(selectedTerm, activeTab, programID): currentEventRsvpAmount = getEventRsvpCountsForTerm(term) volunteerOpportunities = getVolunteerOpportunities(term) countUpcomingVolunteerOpportunities = getUpcomingVolunteerOpportunitiesCount(term, currentTime) + countPastVolunteerOpportunities = getpastVolunteerOpportunitiesCount(term, currentTime) trainingEvents = getTrainingEvents(term, g.current_user) engagementEvents = getEngagementEvents(term) bonnerEvents = getBonnerEvents(term) @@ -109,6 +110,7 @@ def events(selectedTerm, activeTab, programID): # Get the count of all term events for each category to display in the event list page. volunteerOpportunitiesCount: int = len(studentEvents) + countPastVolunteerOpportunitiesCount: int = len(countPastVolunteerOpportunities) trainingEventsCount: int = len(trainingEvents) engagementEventsCount: int = len(engagementEvents) bonnerEventsCount: int = len(bonnerEvents) @@ -133,6 +135,7 @@ def events(selectedTerm, activeTab, programID): if request.headers.get('X-Requested-With') == 'XMLHttpRequest': return jsonify({ "volunteerOpportunitiesCount": volunteerOpportunitiesCount, + "countPastVolunteerOpportunitiesCount": countPastVolunteerOpportunitiesCount, "trainingEventsCount": trainingEventsCount, "engagementEventsCount": engagementEventsCount, "bonnerEventsCount": bonnerEventsCount, @@ -155,6 +158,7 @@ def events(selectedTerm, activeTab, programID): programID = int(programID), managersProgramDict = managersProgramDict, countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities, + countPastVolunteerOpportunities = countPastVolunteerOpportunities, toggleState = toggleState, ) diff --git a/app/templates/events/eventList.html b/app/templates/events/eventList.html index 18a169c17..a2b3c312f 100644 --- a/app/templates/events/eventList.html +++ b/app/templates/events/eventList.html @@ -51,13 +51,13 @@

Events List for {{selectedTerm.description}}