Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
31db0f2
Same message + format as the other tabs
bakobagassas Dec 17, 2025
e2a048d
standardized display when there are no events in V.Opportunities
bakobagassas Dec 17, 2025
6adf2ca
new function for the past volunteer opportunities events
bakobagassas Dec 22, 2025
2f05e56
used the new function in routes and hmtl to display the number of pas…
bakobagassas Dec 22, 2025
e0831f0
fixed the error when there are 0 past events
bakobagassas Dec 22, 2025
f232fe7
Now the correct number displays when it is a past event and also when…
bakobagassas Dec 23, 2025
f19d0a6
removed unused variable
bakobagassas Dec 24, 2025
0acbbdb
test for new function
bakobagassas Dec 24, 2025
636d130
removed variables
bakobagassas Dec 25, 2025
e1701f3
matched variables
bakobagassas Dec 31, 2025
27aed53
updated comment
bakobagassas Dec 31, 2025
08cfb47
Merge branch 'development' into EventsHeader
bakobagassas Jan 23, 2026
7c0259e
Merge branch 'development' into EventsHeader
bakobagassas Feb 2, 2026
7f2f2ef
fixed camel case
bakobagassas Feb 4, 2026
bc5aa0e
camel case
bakobagassas Feb 4, 2026
f09cb00
Merge branch 'EventsHeader' of https://github.com/BCStudentSoftwareDe…
bakobagassas Feb 4, 2026
d959ebf
Merge branch 'development' into EventsHeader
bakobagassas Feb 4, 2026
96dff34
Merge branch 'development' into EventsHeader
Karina-Agliullova Mar 24, 2026
9d9c0fb
Fixed minor issues
Karina-Agliullova Mar 25, 2026
52160d6
Fixed a toggle check issue
Karina-Agliullova Mar 25, 2026
2c61375
Merge branch 'development' into EventsHeader
Karina-Agliullova Mar 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/controllers/main/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -109,6 +110,8 @@ 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)
countUpcomingVolunteerOpportunitiesCount: int = len(countUpcomingVolunteerOpportunities)
countPastVolunteerOpportunitiesCount: int = len(countPastVolunteerOpportunities)
trainingEventsCount: int = len(trainingEvents)
engagementEventsCount: int = len(engagementEvents)
bonnerEventsCount: int = len(bonnerEvents)
Expand All @@ -133,6 +136,8 @@ def events(selectedTerm, activeTab, programID):
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
"volunteerOpportunitiesCount": volunteerOpportunitiesCount,
"countPastVolunteerOpportunitiesCount": countPastVolunteerOpportunitiesCount,
"countUpcomingVolunteerOpportunitiesCount": countUpcomingVolunteerOpportunitiesCount,
"trainingEventsCount": trainingEventsCount,
"engagementEventsCount": engagementEventsCount,
"bonnerEventsCount": bonnerEventsCount,
Expand All @@ -155,6 +160,7 @@ def events(selectedTerm, activeTab, programID):
programID = int(programID),
managersProgramDict = managersProgramDict,
countUpcomingVolunteerOpportunities = countUpcomingVolunteerOpportunities,
countPastVolunteerOpportunities = countPastVolunteerOpportunities,
toggleState = toggleState,
)

Expand Down
26 changes: 26 additions & 0 deletions app/logic/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 21 additions & 2 deletions app/static/js/eventList.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,35 @@ function updateIndicatorCounts(isChecked){
},
success: function(eventsCount) {
const volunteerOpportunitiesCount = Number(eventsCount.volunteerOpportunitiesCount);
const upcomingVolunteerCount = Number(eventsCount.countUpcomingVolunteerOpportunitiesCount);
const trainingEventsCount = Number(eventsCount.trainingEventsCount);
const engagementEventsCount = Number(eventsCount.engagementEventsCount);
const bonnerEventsCount = Number(eventsCount.bonnerEventsCount);
const celtsLaborCount = Number(eventsCount.celtsLaborCount);
const toggleStatus = eventsCount.toggleStatus;

$("#viewPastEventsToggle").prop(toggleStatus, true);

// use ternary operators to populate the tab with a number if there are events, and clear the count if there are none
volunteerOpportunitiesCount > 0 ? $("#volunteerOpportunities").html(`Volunteer Opportunities (${volunteerOpportunitiesCount})`) : $("#volunteerOpportunities").html(`Volunteer Opportunities`)
if (toggleStatus === "checked") {
// Toggle ON: show total (upcoming + past)
if (volunteerOpportunitiesCount > 0) {
$("#volunteerOpportunities").html(
`Volunteer Opportunities (${volunteerOpportunitiesCount})`
);
} else {
$("#volunteerOpportunities").html(`Volunteer Opportunities`);
}
} else {
// Toggle OFF: show upcoming only
if (upcomingVolunteerCount > 0) {
$("#volunteerOpportunities").html(
`Volunteer Opportunities (${upcomingVolunteerCount})`
);
} else {
$("#volunteerOpportunities").html(`Volunteer Opportunities`);
}
}
trainingEventsCount > 0 ? $("#trainingEvents").html(`Trainings (${trainingEventsCount})`) : $("#trainingEvents").html(`Trainings`)
engagementEventsCount > 0 ? $("#engagementEvents").html(`Education and Engagement (${engagementEventsCount})`) : $("#engagementEvents").html('Education and Engagement')
bonnerEventsCount > 0 ? $("#bonnerScholarsEvents").html(`Bonner Scholars (${bonnerEventsCount})`) : $("#bonnerScholarsEvents").html(`Bonner Scholars`)
Expand Down
62 changes: 38 additions & 24 deletions app/templates/events/eventList.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ <h1 class="text-center">Events List for {{selectedTerm.description}}</h1>
<ul class="nav nav-tabs nav-fill mx-2 mb-2" id="pills-tab" role="tablist">
<li class="col-md-2 col-12 nav-item" role="presentation">
<button class="nav-link {{'active' if activeTab == 'volunteerOpportunities' else ''}}" id="volunteerOpportunities"
data-bs-toggle="pill" data-bs-target="#pills-volunteer-opportunities" type="button" role="tab" aria-controls="pills-volunteer-opportunities" aria-selected="true">Volunteer Opportunities</button>
data-bs-toggle="pill" data-bs-target="#pills-volunteer-opportunities" type="button" role="tab" aria-controls="pills-volunteer-opportunities" aria-selected="true">Volunteer Opportunities </button>
</li>

{% if trainingEvents and trainingEvents|length %}
<li class="col-md-2 col-12 nav-item" role="presentation">
<button class="nav-link {{'active' if activeTab == 'trainingEvents' else ''}}" id="trainingEvents"
data-bs-toggle="pill" data-bs-target="#pills-training" type="button" role="tab" aria-controls="pills-training" aria-selected="false">Training</button>
data-bs-toggle="pill" data-bs-target="#pills-training" type="button" role="tab" aria-controls="pills-training" aria-selected="false">Trainings</button>
</li>
{% endif %}

Expand Down Expand Up @@ -175,29 +175,29 @@ <h1 class="text-center">Events List for {{selectedTerm.description}}</h1>
<p class="m-2 fs-4">There are no {{typemap[type]}} events for this term.</p>
{% endif %}
{% endmacro %}

<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show {{'active' if activeTab == 'volunteerOpportunities' else ''}}" id="pills-volunteer-opportunities" role="tabpanel" aria-labelledby="pills-volunteer-opportunities-tab">
{% if volunteerOpportunities %}
<div class="accordion" id="categoryAccordion">
{% for program,events in volunteerOpportunities.items() %}
<div class="accordion-item">
<div class="accordion-header" id="accordion__header_{{program}}">
<button class="accordion-button {{'show' if programID == program.id else 'collapsed'}}"
type="button"
data-bs-toggle="collapse"
data-bs-target="#accordion__body_{{program}}_num_{{ loop.index }}"
aria-expanded="true"
aria-controls="accordion__body_{{program}}">
{{program.programName}}
{% if program.id not in countUpcomingVolunteerOpportunities%}
<span class="ms-auto fw-light fst-italic">0 upcoming events</span>
{% else %}
<span class="ms-auto fw-light fst-italic">{{countUpcomingVolunteerOpportunities[program.id]}} upcoming event{% if countUpcomingVolunteerOpportunities[program.id] > 1 %}s{% endif %}</span>
{% endif %}
<div class="tab-pane fade show {{ 'active' if activeTab == 'volunteerOpportunities' else '' }}"
id="pills-volunteer-opportunities"
role="tabpanel"
aria-labelledby="pills-volunteer-opportunities-tab">

</button>
</div>
{% if volunteerOpportunities %}
<div class="accordion" id="categoryAccordion">
{% for program, events in volunteerOpportunities.items() %}
<div class="accordion-item">
<div class="accordion-header" id="accordion__header_{{ program }}">
<button class="accordion-button {{ 'show' if programID == program.id else 'collapsed' }}"
type="button"
data-bs-toggle="collapse"
data-bs-target="#accordion__body_{{ program }}_num_{{ loop.index }}"
aria-expanded="true"
aria-controls="accordion__body_{{ program }}">
{{ program.programName }}
{% set upcoming = countUpcomingVolunteerOpportunities.get(program.id, 0) %}
{% set past = countPastVolunteerOpportunities.get(program.id, 0) %}
<span class="ms-auto fw-light fst-italic"> {{ upcoming }} upcoming event{% if upcoming != 1 %}s{% endif %} and {{ past }} past event{% if past != 1 %}s{% endif %} </span>
</button>
</div>
<div id="accordion__body_{{program}}_num_{{ loop.index }}"
class="accordion-collapse collapse {{'show' if programID == program.id else ''}}"
aria-labelledby="accordion__header_{{program}}"
Expand All @@ -208,7 +208,21 @@ <h1 class="text-center">Events List for {{selectedTerm.description}}</h1>
{% endfor %}
</div>
{% else %}
<p class="m-2 fs-4">There are no events that earn service for this term.</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Program</th>
<th scope="col">Event Name</th>
<th scope="col">Date</th>
<th scope="col">Time</th>
<th scope="col">Location</th>
<th scope="col"></th>
</tr>
</thead>
</table>
</div>
<td colspan="{{colspan_value}}" class="p-3 no-upcoming">There are no upcoming events for this program</td>
{% endif %}
</div>
<div class="tab-pane fade show" id="pills-training" role="tabpanel" aria-labelledby="pills-training-tab">
Expand Down
87 changes: 86 additions & 1 deletion tests/code/test_event_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from app.models.term import Term
from app.models.user import User
from app.models.eventViews import EventView
from app.logic.events import getVolunteerOpportunities, getEngagementEvents, getTrainingEvents, getBonnerEvents, getCeltsLabor, addEventView, getUpcomingVolunteerOpportunitiesCount
from app.logic.events import getVolunteerOpportunities, getEngagementEvents, getTrainingEvents, getBonnerEvents, getCeltsLabor, addEventView, getUpcomingVolunteerOpportunitiesCount, getpastVolunteerOpportunitiesCount

@pytest.mark.integration
@pytest.fixture
Expand Down Expand Up @@ -154,6 +154,91 @@ def test_getUpcomingVolunteerOpportunitiesCount():

transaction.rollback()

@pytest.mark.integration
def test_getPastVolunteerOpportunitiesCount():
with mainDB.atomic() as transaction:
testDate = datetime.strptime("2021-08-01 05:00", "%Y-%m-%d %H:%M")
currentTestTerm = Term.get_by_id(5)

# Move any existing term-5 events into the future
Event.update(startDate=date(2021, 8, 5)).where(Event.term_id == 5).execute()

# Past Volunteer Opportunity (AGP)
pastAgpEvent = Event.create(
name="Test past AGP event",
term=currentTestTerm,
description="Past volunteer opportunity (AGP).",
timeStart="03:00:00",
timeEnd="04:00:00",
location="Mars",
isTraining=False,
isService=True,
startDate="2021-07-31",
program=3
)

# Past Volunteer Opportunity (same day, before test time)
Event.create(
name="Test same-day past AGP event",
term=currentTestTerm,
description="Same day but earlier time.",
timeStart="04:00:00",
timeEnd="04:30:00",
location="Venus",
isTraining=False,
isService=True,
startDate="2021-08-01",
program=3
)

# Future Volunteer Opportunity (should NOT be counted)
Event.create(
name="Test future AGP event",
term=currentTestTerm,
description="Future volunteer opportunity.",
timeStart="06:00:00",
timeEnd="07:00:00",
location="Moon",
isTraining=False,
isService=True,
startDate="2021-08-02",
program=3
)

# Verify two past AGP events
pastVolunteerOpportunities = getpastVolunteerOpportunitiesCount(
currentTestTerm, testDate
)
assert pastVolunteerOpportunities == {3: 2}

# Cancel one past event → should reduce count
Event.update(isCanceled=True).where(Event.id == pastAgpEvent.id).execute()
pastVolunteerOpportunities = getpastVolunteerOpportunitiesCount(
currentTestTerm, testDate
)
assert pastVolunteerOpportunities == {3: 1}

# Create past event for another program (Buddies)
pastBuddiesEvent = Event.create(
name="Test past Buddies event",
term=currentTestTerm,
description="Past volunteer opportunity (Buddies).",
timeStart="02:00:00",
timeEnd="03:00:00",
location="Earth",
isTraining=False,
isService=True,
startDate="2021-07-30",
program=2
)

pastVolunteerOpportunities = getpastVolunteerOpportunitiesCount(
currentTestTerm, testDate
)
assert pastVolunteerOpportunities == {2: 1, 3: 1}

transaction.rollback()

@pytest.mark.integration
def test_getTrainingEvents(training_events):
with mainDB.atomic() as transaction:
Expand Down