From d89a763f0e87e7ab7cd9f831815071a77c340ca7 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 6 Apr 2025 14:55:04 -0400 Subject: [PATCH 01/15] copied changes from min_gpa branch --- tcf_website/templates/search/searchbar.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 3e4c1ef8b..8fe64a3a6 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -110,6 +110,18 @@
Only display courses with open sections +
+
+ + +
+
+ +
From f33889a1708de1fa3ea5e70dbf1fb5ab26c60e1b Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 13 Apr 2025 13:00:33 -0400 Subject: [PATCH 02/15] moved changes from min_gpa to gpa-search due to conflicts --- tcf_core/context_processors.py | 1 + tcf_website/models/models.py | 14 ++ tcf_website/templates/search/searchbar.html | 174 ++++++++++++++++++++ tcf_website/views/search.py | 5 + 4 files changed, 194 insertions(+) diff --git a/tcf_core/context_processors.py b/tcf_core/context_processors.py index 063cf550b..17623d217 100644 --- a/tcf_core/context_processors.py +++ b/tcf_core/context_processors.py @@ -63,5 +63,6 @@ def searchbar_context(request): "from_time": saved_filters.get("from_time", ""), "to_time": saved_filters.get("to_time", ""), "open_sections": saved_filters.get("open_sections", False), + "min_gpa": saved_filters.get("min_gpa", "") } return context diff --git a/tcf_website/models/models.py b/tcf_website/models/models.py index 656d51acd..8cd6b22ca 100644 --- a/tcf_website/models/models.py +++ b/tcf_website/models/models.py @@ -704,6 +704,20 @@ def filter_by_time(cls, days=None, start_time=None, end_time=None): query = query.filter(section_conditions) return query.distinct() + + @classmethod + def filter_by_gpa(cls, min_gpa=None): + """Filter courses by minimum GPA.""" + query = cls.objects.all() + + # Apply GPA filtering if min_gpa is provided + if min_gpa is not None: + # Calculate the average GPA for each course using the related CourseGrade model + query = query.annotate( + avg_gpa=Avg("coursegrade__average") + ).filter(avg_gpa__gte=min_gpa) + + return query.distinct() @classmethod def filter_by_open_sections(cls): diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 8fe64a3a6..23e57059c 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -243,6 +243,7 @@
const timeFrom = document.getElementById('from_time'); const timeTo = document.getElementById('to_time'); const openSections = document.getElementById('open-sections'); + const gpa_input = document.getElementById('gpa-input'); // Check initial state (in case of page refresh with active filters) updateButtonState(); @@ -259,6 +260,7 @@
timeTo.addEventListener('input', updateButtonState); openSections.addEventListener('change', updateButtonState); + gpa_input.addEventListener('input', updateButtonState); // Checks for active filters or inactive day filters to determine button state function updateButtonState() { @@ -280,6 +282,10 @@
openSectionsChanged = true; } + if (gpa_input.value) { + gpaChanged = gpa_input.value !== ''; + } + if (activeFilters.length > 0 || activeDayFilters.length > 0 || timeFromChanged || timeToChanged || openSectionsChanged) { filterButton.classList.add('filter-active'); filterButton.textContent = 'Filters Active'; @@ -388,6 +394,9 @@
document.getElementById('from_time').value = ''; document.getElementById('to_time').value = ''; + // Clear GPA dropdown + document.getElementById('gpa-input').value = ''; + updateWeekdays(); updateButtonState(); @@ -426,6 +435,104 @@
}); }); + // Fill the Min GPA dropdown with options from 2.0 to 4.0 with 0.2 increments + const gpa_dropdown = document.getElementById('gpa-dropdown'); + const minGPAButton = document.getElementById('gpa-dropdown-toggle'); + // gpa_input cached before code that checks for applied filters status + + // Create an array of GPA values to fill GPA dropdown from 4.0 to 2 with 0.2 increments (including 3.9 and 3.7) + const gpaValues = []; + for (let i = 40; i >= 20; i -= 2) { + const gpa = (i / 10).toFixed(1); + gpaValues.push(gpa); + } + + gpaValues.push('3.9', '3.7'); + + // Sort numerically if needed + gpaValues.sort((a, b) => parseFloat(b) - parseFloat(a)); + + // Create a document fragment for better performance when appending to GPA dropdown + const fragment = document.createDocumentFragment(); + + // Appends each GPA value as an element to the dropdown + gpaValues.forEach(value => { + const option = document.createElement('div'); + option.className = 'gpa-option'; + option.textContent = value; + option.dataset.value = value; + + option.addEventListener('click', () => { + gpa_input.value = option.dataset.value; + updateButtonState(); + toggleGPADropdown(false); + }); + + fragment.appendChild(option); + }); + + // Append all options at once for better performance + gpa_dropdown.appendChild(fragment); + + // Stops invalid inputs from being entered into min GPA input (only numbers and decimal) + gpa_input.addEventListener('input', function() { + // Use a more efficient regex that handles all cases + this.value = this.value.replace(/[^0-9.]/g, ''); + const parts = this.value.split('.'); + if (parts.length > 2) { + this.value = parts[0] + '.' + parts.slice(1).join(''); + } + }); + + // Input validation for GPA field + gpa_input.addEventListener('input', function() { + // Use a more efficient regex that handles all cases + this.value = this.value.replace(/[^0-9.]/g, ''); + + // More efficient way to handle multiple decimals + const parts = this.value.split('.'); + if (parts.length > 2) { + this.value = parts[0] + '.' + parts.slice(1).join(''); + } + }); + + dropdown.addEventListener('click', function(event) { + hideGPADropdown(); + }); + + function showGPADropdown() { + gpa_dropdown.style.display = 'block'; + } + + function hideGPADropdown() { + gpa_dropdown.style.display = 'none'; + } + + // Toggle function using the show/hide functions + function toggleGPADropdown() { + if (gpa_dropdown.style.display === 'block') { + hideGPADropdown(); + } else { + showGPADropdown(); + } + } + + // Use event delegation for click events + minGPAButton.addEventListener('click', function(e) { + e.stopPropagation(); // Prevent dropdown closing from dropdown listener + toggleGPADropdown(); + }); + + gpa_input.addEventListener('click', function(e) { + e.stopPropagation(); // Prevent dropdown closing from dropdown listener + toggleGPADropdown(); + }); + + // Global document click handler to close dropdown + document.addEventListener('click', function() { + hideGPADropdown(); + }); + // Initialize weekdays updateWeekdays(); @@ -756,4 +863,71 @@
opacity: 0; visibility: hidden; } + + .min-gpa-div { + display: inline-flex; + gap: 0; + } + + .min-gpa-input{ + padding: 8px 12px; + box-sizing: border-box; + border: 1px solid #ccc; + border-radius: 4px 0 0 4px; + } + + .gpa-input-container { + position: relative; + display: flex; + width: 100%; + } + + .gpa-dropdown { + position: absolute; + top: 100%; + left: 0; + width: 100%; + background-color: white; + border: 1px solid #ccc; + max-height: 200px; + overflow-y: auto; + display: none; + z-index: 1000; + border-radius: 4px 4px 4px 4px; + box-sizing: border-box; + } + + .gpa-option { + padding: 8px 12px; + cursor: pointer; + } + + .gpa-option:hover { + background-color: #f1f1f1; + transition: all 0.15s ease; + } + + .gpa-dropdown-button { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + background-color: #f8f9fa; + border-radius: 0 4px 4px 0; + border: 1px solid #ccc; + cursor: pointer; + outline: 0; + outline: none; + } + + .gpa-dropdown-button:hover { + background-color: #e2e6ea; + transition: all 0.15s ease; + } + + .button:focus { + outline: none; + + } diff --git a/tcf_website/views/search.py b/tcf_website/views/search.py index 08579dcc9..212dcf5b3 100644 --- a/tcf_website/views/search.py +++ b/tcf_website/views/search.py @@ -46,6 +46,7 @@ def search(request): "from_time": request.GET.get("from_time"), "to_time": request.GET.get("to_time"), "open_sections": request.GET.get("open_sections") == "on", + "min_gpa": request.GET.get("min_gpa") } # Save filters to session @@ -236,4 +237,8 @@ def apply_filters(results, filters): time_filtered = Course.filter_by_time(days=weekdays, start_time=from_time, end_time=to_time) results = results.filter(id__in=time_filtered.values_list("id", flat=True)) + min_gpa = filters.get("min_gpa") + if filters.get("min_gpa"): + gpa_filtered = Course.filter_by_gpa(min_gpa=min_gpa) + results = results.filter(id__in=gpa_filtered.values_list("id", flat=True)) return results From 01f4b6a9c388d9ef358bf56d3d5baa7a7e2fbca5 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 13 Apr 2025 13:24:50 -0400 Subject: [PATCH 03/15] minor changes to fix styling --- tcf_website/templates/search/searchbar.html | 47 +++++++++++---------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 23e57059c..9e6466eaf 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -98,29 +98,32 @@
Availability
-
- - -
-
-
- - -
+
+
+ + +
+ +
+
+ + +
+
+
-
From f50d80a2e25b446e73b6e4c7f704c65c9c2e1895 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 13 Apr 2025 13:29:55 -0400 Subject: [PATCH 04/15] fixed styling with availability and gpa search --- tcf_website/templates/search/searchbar.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 9e6466eaf..1d68fb13d 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -99,7 +99,7 @@
Availability
-
+
gap: 0; } + + .availability-check { + display: inline-flex; + justify-content: left; + border-radius: 4px; + padding: 0.375rem 0.75rem; + border: 1px solid #ccc; + height: 100%; + } + .min-gpa-input{ padding: 8px 12px; box-sizing: border-box; From abe2422a13f33a6bf503b1723ff2435ae874add7 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 13 Apr 2025 13:45:57 -0400 Subject: [PATCH 05/15] fixed issue with gpa input javascript --- tcf_website/templates/search/searchbar.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 1d68fb13d..600e15dd4 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -273,6 +273,7 @@
let timeFromChanged = false; let timeToChanged = false; let openSectionsChanged = false; + let gpaChanged = false; if (timeFrom.value) { timeFromChanged = timeFrom.value !== ''; @@ -289,7 +290,7 @@
gpaChanged = gpa_input.value !== ''; } - if (activeFilters.length > 0 || activeDayFilters.length > 0 || timeFromChanged || timeToChanged || openSectionsChanged) { + if (activeFilters.length > 0 || activeDayFilters.length > 0 || timeFromChanged || timeToChanged || openSectionsChanged || gpaChanged) { filterButton.classList.add('filter-active'); filterButton.textContent = 'Filters Active'; } else { From f7922c56358cc38149e93fdf50bbff23f25a36a4 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 20 Apr 2025 11:52:40 -0400 Subject: [PATCH 06/15] some minor fixes to styling and white space --- tcf_website/models/models.py | 1 - tcf_website/templates/search/searchbar.html | 10 ++++++---- tcf_website/views/search.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tcf_website/models/models.py b/tcf_website/models/models.py index 4d78d4e53..871c8e6ac 100644 --- a/tcf_website/models/models.py +++ b/tcf_website/models/models.py @@ -750,7 +750,6 @@ def filter_by_gpa(cls, min_gpa=None): query = query.annotate( avg_gpa=Avg("coursegrade__average") ).filter(avg_gpa__gte=min_gpa) - return query.distinct() @classmethod diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 600e15dd4..28a0afd03 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -94,11 +94,13 @@
-
+
Availability
-
+
+ Minimum GPA +
-
@@ -608,7 +609,7 @@
.filter-grid { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 1.5rem; + gap: 0.3rem 1.5rem; align-items: start; } @@ -888,6 +889,7 @@
box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px 0 0 4px; + width: 100%; } .gpa-input-container { diff --git a/tcf_website/views/search.py b/tcf_website/views/search.py index a9b32721a..a76c46fd7 100644 --- a/tcf_website/views/search.py +++ b/tcf_website/views/search.py @@ -250,7 +250,7 @@ def apply_filters(results, filters): results = results.filter(id__in=time_filtered.values_list("id", flat=True)) min_gpa = filters.get("min_gpa") - if filters.get("min_gpa"): + if filters.get("min_gpa"): gpa_filtered = Course.filter_by_gpa(min_gpa=min_gpa) results = results.filter(id__in=gpa_filtered.values_list("id", flat=True)) return results From 2e8fcd2ba879957657ab23654011de132201d919 Mon Sep 17 00:00:00 2001 From: YuDavidCao Date: Sun, 20 Apr 2025 12:34:08 -0400 Subject: [PATCH 07/15] fixing mobile filter --- tcf_website/templates/search/searchbar.html | 70 ++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 600e15dd4..fdebd9371 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -93,8 +93,40 @@
- +
+
+ Availability +
+
+ + +
+
+
+
+ Minimum GPA +
+
+ + +
+
+
+ +
Availability
@@ -883,6 +915,42 @@
height: 100%; } + .availability-section { + display: none; + } + + /* Show it only on mobile devices (screen width less than 768px) */ + @media (max-width: 768px) { + .availability-section { + display: block; + } + + .middle-filter-section { + display: none !important; + } + + .availability-check { + display: flex; + align-items: center; + justify-content: flex-start; + border-radius: 4px; + padding: 0.375rem 0.75rem; + border: 1px solid #ccc; + height: fit-content; + width: fit-content; + gap: 0.5rem; + } + + .availability-check .form-check-input { + margin: 0; + } + + .availability-check .form-check-label { + margin-left: 1.2rem; + white-space: normal; + } + } + .min-gpa-input{ padding: 8px 12px; box-sizing: border-box; From 1e3a7f777ce54ff8b18fd3abdd6ee311313355b7 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 20 Apr 2025 13:25:47 -0400 Subject: [PATCH 08/15] updated styling of slider and cleared out old js and styling for dropdown --- tcf_website/templates/search/searchbar.html | 190 ++++++-------------- 1 file changed, 54 insertions(+), 136 deletions(-) diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index 28a0afd03..61825f826 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -116,15 +116,24 @@
- - -
+ +
-
@@ -247,7 +256,7 @@
const timeFrom = document.getElementById('from_time'); const timeTo = document.getElementById('to_time'); const openSections = document.getElementById('open-sections'); - const gpa_input = document.getElementById('gpa-input'); + const gpaInput = document.getElementById('gpa-input'); // Check initial state (in case of page refresh with active filters) updateButtonState(); @@ -264,7 +273,7 @@
timeTo.addEventListener('input', updateButtonState); openSections.addEventListener('change', updateButtonState); - gpa_input.addEventListener('input', updateButtonState); + gpaInput.addEventListener('input', updateButtonState); // Checks for active filters or inactive day filters to determine button state function updateButtonState() { @@ -287,8 +296,8 @@
openSectionsChanged = true; } - if (gpa_input.value) { - gpaChanged = gpa_input.value !== ''; + if (gpaInput.value) { + gpaChanged = gpaInput.value !== ''; } if (activeFilters.length > 0 || activeDayFilters.length > 0 || timeFromChanged || timeToChanged || openSectionsChanged || gpaChanged) { @@ -432,6 +441,26 @@
}); }); + const gpaSlider = document.getElementById('gpa-slider'); + // Update input when slider changes + gpaSlider.addEventListener('input', () => { + gpaInput.value = gpaSlider.value; + }); + + // Optional: Update slider if user types in the input + gpaInput.addEventListener('input', () => { + const val = parseFloat(gpaInput.value); + if (!isNaN(val) && val >= 1.0 && val <= 4.0) { + gpaSlider.value = val; + } + else if (val > 4.0) { + gpaSlider.value = 4.0; + } + else if (val < 1.0) { + gpaSlider.value = 1.0; + } + }); + // Update weekdays on checkbox change document.querySelectorAll('.day-checkbox').forEach(checkbox => { checkbox.addEventListener('change', () => { @@ -440,47 +469,8 @@
}); }); - // Fill the Min GPA dropdown with options from 2.0 to 4.0 with 0.2 increments - const gpa_dropdown = document.getElementById('gpa-dropdown'); - const minGPAButton = document.getElementById('gpa-dropdown-toggle'); - // gpa_input cached before code that checks for applied filters status - - // Create an array of GPA values to fill GPA dropdown from 4.0 to 2 with 0.2 increments (including 3.9 and 3.7) - const gpaValues = []; - for (let i = 40; i >= 20; i -= 2) { - const gpa = (i / 10).toFixed(1); - gpaValues.push(gpa); - } - - gpaValues.push('3.9', '3.7'); - - // Sort numerically if needed - gpaValues.sort((a, b) => parseFloat(b) - parseFloat(a)); - - // Create a document fragment for better performance when appending to GPA dropdown - const fragment = document.createDocumentFragment(); - - // Appends each GPA value as an element to the dropdown - gpaValues.forEach(value => { - const option = document.createElement('div'); - option.className = 'gpa-option'; - option.textContent = value; - option.dataset.value = value; - - option.addEventListener('click', () => { - gpa_input.value = option.dataset.value; - updateButtonState(); - toggleGPADropdown(false); - }); - - fragment.appendChild(option); - }); - - // Append all options at once for better performance - gpa_dropdown.appendChild(fragment); - // Stops invalid inputs from being entered into min GPA input (only numbers and decimal) - gpa_input.addEventListener('input', function() { + gpaInput.addEventListener('input', function() { // Use a more efficient regex that handles all cases this.value = this.value.replace(/[^0-9.]/g, ''); const parts = this.value.split('.'); @@ -490,7 +480,7 @@
}); // Input validation for GPA field - gpa_input.addEventListener('input', function() { + gpaInput.addEventListener('input', function() { // Use a more efficient regex that handles all cases this.value = this.value.replace(/[^0-9.]/g, ''); @@ -501,43 +491,6 @@
} }); - dropdown.addEventListener('click', function(event) { - hideGPADropdown(); - }); - - function showGPADropdown() { - gpa_dropdown.style.display = 'block'; - } - - function hideGPADropdown() { - gpa_dropdown.style.display = 'none'; - } - - // Toggle function using the show/hide functions - function toggleGPADropdown() { - if (gpa_dropdown.style.display === 'block') { - hideGPADropdown(); - } else { - showGPADropdown(); - } - } - - // Use event delegation for click events - minGPAButton.addEventListener('click', function(e) { - e.stopPropagation(); // Prevent dropdown closing from dropdown listener - toggleGPADropdown(); - }); - - gpa_input.addEventListener('click', function(e) { - e.stopPropagation(); // Prevent dropdown closing from dropdown listener - toggleGPADropdown(); - }); - - // Global document click handler to close dropdown - document.addEventListener('click', function() { - hideGPADropdown(); - }); - // Initialize weekdays updateWeekdays(); @@ -874,6 +827,14 @@
gap: 0; } + .gpa-slider { + width: 80%; + background: #d75626; + border-radius: 5px; + outline: none; + height: 0.5rem; + margin: 5% 0; + } .availability-check { display: inline-flex; @@ -888,8 +849,9 @@
padding: 8px 12px; box-sizing: border-box; border: 1px solid #ccc; - border-radius: 4px 0 0 4px; - width: 100%; + border-radius: 4px; + width: 35%; + margin-right: 0.5rem; } .gpa-input-container { @@ -898,50 +860,6 @@
width: 100%; } - .gpa-dropdown { - position: absolute; - top: 100%; - left: 0; - width: 100%; - background-color: white; - border: 1px solid #ccc; - max-height: 200px; - overflow-y: auto; - display: none; - z-index: 1000; - border-radius: 4px 4px 4px 4px; - box-sizing: border-box; - } - - .gpa-option { - padding: 8px 12px; - cursor: pointer; - } - - .gpa-option:hover { - background-color: #f1f1f1; - transition: all 0.15s ease; - } - - .gpa-dropdown-button { - width: 100%; - display: flex; - align-items: center; - justify-content: center; - width: 40px; - background-color: #f8f9fa; - border-radius: 0 4px 4px 0; - border: 1px solid #ccc; - cursor: pointer; - outline: 0; - outline: none; - } - - .gpa-dropdown-button:hover { - background-color: #e2e6ea; - transition: all 0.15s ease; - } - .button:focus { outline: none; From 74e8c18fa5aa0182c12ffe7af613a77dae409676 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Sun, 20 Apr 2025 13:26:23 -0400 Subject: [PATCH 09/15] Merge branch 'gpa-search' of https://github.com/thecourseforum/theCourseForum2 into gpa-search From 56e65c21a4a054f18b1ca8bef8b111074c6f06f4 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Fri, 25 Apr 2025 00:12:37 -0400 Subject: [PATCH 10/15] set better default for min gpa in context and implemented inline filtering in models.py for avg gpa --- tcf_core/context_processors.py | 2 +- tcf_website/models/models.py | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/tcf_core/context_processors.py b/tcf_core/context_processors.py index 17623d217..2f642a794 100644 --- a/tcf_core/context_processors.py +++ b/tcf_core/context_processors.py @@ -63,6 +63,6 @@ def searchbar_context(request): "from_time": saved_filters.get("from_time", ""), "to_time": saved_filters.get("to_time", ""), "open_sections": saved_filters.get("open_sections", False), - "min_gpa": saved_filters.get("min_gpa", "") + "min_gpa": saved_filters.get("min_gpa", 2.0) } return context diff --git a/tcf_website/models/models.py b/tcf_website/models/models.py index 71d6dde80..303823fc5 100644 --- a/tcf_website/models/models.py +++ b/tcf_website/models/models.py @@ -756,15 +756,11 @@ def filter_by_time(cls, days=None, start_time=None, end_time=None): @classmethod def filter_by_gpa(cls, min_gpa=None): """Filter courses by minimum GPA.""" - query = cls.objects.all() - - # Apply GPA filtering if min_gpa is provided - if min_gpa is not None: - # Calculate the average GPA for each course using the related CourseGrade model - query = query.annotate( - avg_gpa=Avg("coursegrade__average") - ).filter(avg_gpa__gte=min_gpa) - return query.distinct() + return cls.objects.annotate( + avg_gpa=Avg("coursegrade__average") + ).filter( + avg_gpa__gte=min_gpa if min_gpa is not None else float('-inf') + ) @classmethod def filter_by_open_sections(cls): From 299d08c5358abc46fe4ae918920fffa15a902cf6 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Fri, 25 Apr 2025 10:36:59 -0400 Subject: [PATCH 11/15] minor whitespace fixes and syncing of mobile / desktop for min gpa --- tcf_website/models/models.py | 4 +- tcf_website/templates/search/searchbar.html | 90 ++++++++++++++------- tcf_website/views/search.py | 1 - 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/tcf_website/models/models.py b/tcf_website/models/models.py index 303823fc5..9d53df18b 100644 --- a/tcf_website/models/models.py +++ b/tcf_website/models/models.py @@ -752,7 +752,7 @@ def filter_by_time(cls, days=None, start_time=None, end_time=None): query = query.filter(section_conditions) return query.distinct() - + @classmethod def filter_by_gpa(cls, min_gpa=None): """Filter courses by minimum GPA.""" @@ -1842,4 +1842,4 @@ class ScheduledCourse(models.Model): time = models.CharField(max_length=255) def __str__(self): - return f"{self.section.course} | {self.instructor}" + return f"{self.section.course} | {self.instructor}" \ No newline at end of file diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index bf087f359..fb3f8f455 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -93,6 +93,8 @@
+ +
@@ -115,14 +117,9 @@
Minimum GPA
-
- - -
+
+ +
@@ -148,23 +145,8 @@
- - + +
@@ -288,7 +270,56 @@
const timeFrom = document.getElementById('from_time'); const timeTo = document.getElementById('to_time'); const openSections = document.getElementById('open-sections'); - const gpaInput = document.getElementById('gpa-input'); + + const gpaInputDesktop = document.getElementById('gpa-input'); + const gpaSliderDesktop = document.getElementById('gpa-slider'); + const gpaInputMobile = document.getElementById('gpa-input-compact'); + const gpaSliderMobile = document.getElementById('gpa-slider-compact'); + const hiddenGpaInput = document.getElementById('min_gpa'); + + // Function to sync all GPA values + function syncGpaValues(value) { + gpaInputDesktop.value = value; + gpaSliderDesktop.value = value; + gpaInputMobile.value = value; + gpaSliderMobile.value = value; + hiddenGpaInput.value = value; + } + + // Initialize with any existing value + if (hiddenGpaInput.value) { + syncGpaValues(hiddenGpaInput.value); + } + + // Add event listeners to all inputs + gpaInputDesktop.addEventListener('input', function() { + syncGpaValues(this.value); + }); + + gpaSliderDesktop.addEventListener('input', function() { + syncGpaValues(this.value); + }); + + gpaInputMobile.addEventListener('input', function() { + syncGpaValues(this.value); + }); + + gpaSliderMobile.addEventListener('input', function() { + syncGpaValues(this.value); + }); + + // Form submission - ensure the hidden input has the current value + document.querySelector('form').addEventListener('submit', function() { + const currentValue = window.innerWidth < 768 ? + gpaInputMobile.value : gpaInputDesktop.value; + hiddenGpaInput.value = currentValue; + }); + + // Handle window resize (optional - already handled by syncing values) + window.addEventListener('resize', function() { + // This is optional since values are already synced + // Could be used if there's specific logic needed on resize + }); // Check initial state (in case of page refresh with active filters) updateButtonState(); @@ -305,7 +336,7 @@
timeTo.addEventListener('input', updateButtonState); openSections.addEventListener('change', updateButtonState); - gpaInput.addEventListener('input', updateButtonState); + hiddenGpaInput.addEventListener('input', updateButtonState); // Checks for active filters or inactive day filters to determine button state function updateButtonState() { @@ -328,8 +359,8 @@
openSectionsChanged = true; } - if (gpaInput.value) { - gpaChanged = gpaInput.value !== ''; + if (hiddenGpaInput.value) { + gpaChanged = hiddenGpaInput.value !== ''; } if (activeFilters.length > 0 || activeDayFilters.length > 0 || timeFromChanged || timeToChanged || openSectionsChanged || gpaChanged) { @@ -441,6 +472,7 @@
document.getElementById('to_time').value = ''; // Clear GPA dropdown + document.getElementById('min-gpa-input').value = ''; document.getElementById('gpa-input').value = ''; updateWeekdays(); diff --git a/tcf_website/views/search.py b/tcf_website/views/search.py index a76c46fd7..bb3a1e2d3 100644 --- a/tcf_website/views/search.py +++ b/tcf_website/views/search.py @@ -182,7 +182,6 @@ def normalize_search_query(q: str) -> str: } for course in results ] - return courses From 456067c94b8cbe12f89abd01a32de866c3632913 Mon Sep 17 00:00:00 2001 From: Brian Tran Date: Fri, 25 Apr 2025 16:02:09 -0400 Subject: [PATCH 12/15] synced mobile and desktop view for min gpa and availability along with styling fixes --- tcf_website/templates/search/searchbar.html | 65 +++++++++------------ 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html index fb3f8f455..0a5386fef 100644 --- a/tcf_website/templates/search/searchbar.html +++ b/tcf_website/templates/search/searchbar.html @@ -104,11 +104,10 @@
-