Skip to content

Commit adea542

Browse files
authored
Implemented Cally to replace JQuery based daterangepicker (#4568)
Fixes #4475. This removes the existing JQuery-based daterangepicker in favor of implementing the DaisyUI supported [Cally](https://wicky.nillia.ms/cally/). This PR builds off of the theme changes of #4543 and requires that to be merged first. Also added functionality that will ensure the calendars auto-populate ranges if date filters exist (for both submission all AND `table_filter_and_search` uses)
1 parent b9260f9 commit adea542

16 files changed

Lines changed: 114 additions & 2133 deletions

File tree

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ download-esm-modules: ## Download ECMAScript modules for the project
7474
download-esm @github/relative-time-element $(JS_ESM_DIR)
7575
download-esm @github/filter-input-element $(JS_ESM_DIR)
7676
download-esm choices.js $(JS_ESM_DIR)
77+
download-esm cally $(JS_ESM_DIR)
7778

7879

7980
.cache/tandem: ## Install tandem, a tool for running multiple commands in parallel
@@ -100,6 +101,4 @@ download-esm-modules: ## Download ECMAScript modules for the project
100101
cp node_modules/htmx.org/dist/ext/multi-swap.js $(JS_VENDOR_DIR)/htmx-ext-multi-swap.min.js
101102
cp node_modules/alpinejs/dist/cdn.min.js $(JS_VENDOR_DIR)/alpine.min.js
102103
cp node_modules/@alpinejs/focus/dist/cdn.min.js $(JS_VENDOR_DIR)/alpine-focus.min.js
103-
cp node_modules/daterangepicker/moment.min.js $(JS_VENDOR_DIR)/moment.min.js
104-
cp node_modules/daterangepicker/daterangepicker.js $(JS_VENDOR_DIR)/daterangepicker.min.js
105104
@touch $@

hypha/apply/funds/templates/funds/includes/table_filter_and_search.html

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -103,59 +103,49 @@ <h2 class="section-header">{{ heading }}</h2>
103103
</ul>
104104
{% enddropdown_menu %}
105105
{% else %}
106-
<div id="{{ field_name }}"
107-
class="flex justify-between items-center py-1 w-full font-medium border cursor-pointer md:border-none text-base-content/80 ps-2 pe-2 md:hover:bg-transparent md:hover:text-primary hover:bg-base-200"
108-
data-field-name="{{ field_name }}" data-filter-list>
109-
{{ field.label }}
110-
{% heroicon_micro attributes.icon|default:"chevron-down" aria_hidden="true" size="16" class="hidden md:inline-block size-4 ms-0.5" %}
111-
</div>
106+
{% dropdown_menu title=field.label heading=field|get_dropdown_heading position="right" %}
107+
<calendar-range class="mx-auto cally" onchange="updateURL(this.value, '{{field_name}}')" data-field-name='{{field_name}}'>
108+
{% heroicon_micro "chevron-left" aria_label="Previous" slot="previous" aria_hidden=true size=18 %}
109+
{% heroicon_micro "chevron-right" aria_label="Next" slot="next" aria_hidden=true size=18 %}
110+
<calendar-month></calendar-month>
111+
</calendar-range>
112+
{% enddropdown_menu %}
112113

113-
<script defer src="{% static 'js/vendor/moment.min.js' %}"></script>
114-
<script defer src="{% static 'js/vendor/daterangepicker.min.js' %}"></script>
115114
<script>
116-
document.addEventListener("DOMContentLoaded", function () {
117-
function updateURL(paramStart, paramEnd, startDate, endDate) {
118-
let url = new URL(window.location);
119-
if (startDate) {
120-
url.searchParams.set(paramStart, startDate);
121-
} else {
122-
url.searchParams.delete(paramStart);
123-
}
115+
/**
116+
* Update the current URL with the date params and reload the page
117+
*
118+
* @param {string} dateRange - the date range in the format of `YYYY-MM-DD/YYYY-MM-DD`
119+
* @param {string} fieldName - the field being used as a query param
120+
*/
121+
function updateURL(dateRange, fieldName) {
122+
const [startDate, endDate] = dateRange.split('/');
123+
const paramStart = `${fieldName}_after`
124+
const paramEnd = `${fieldName}_before`
124125

125-
if (endDate) {
126-
url.searchParams.set(paramEnd, endDate);
127-
} else {
128-
url.searchParams.delete(paramEnd);
129-
}
130-
window.history.pushState({}, '', url);
131-
location.reload(); // Reload to apply the filter
132-
}
126+
let url = new URL(window.location);
127+
url.searchParams.set(paramStart, startDate);
128+
url.searchParams.set(paramEnd, endDate);
129+
130+
window.history.pushState({}, '', url);
131+
location.reload(); // Reload to apply the filter
132+
}
133133

134-
var fieldName = "{{ field_name }}"; // Django variable inside JS
135-
let dateLink = $("#{{ field_name }}");
134+
document.addEventListener("DOMContentLoaded", function () {
135+
// Get all date pickers and set their initial range values based on the URL params
136+
document.querySelectorAll(".cally").forEach((datepicker) => {
137+
const fieldName = datepicker.getAttribute("data-field-name");
136138

137-
var start = moment().subtract(29, 'days');
138-
var end = moment();
139+
const params = new URL(window.location).searchParams;
139140

141+
const startDate = params.get(`${fieldName}_after`);
142+
const endDate = params.get(`${fieldName}_before`);
140143

141-
dateLink.daterangepicker({
142-
startDate: start,
143-
endDate: end,
144-
ranges: {
145-
'Today': [moment(), moment()],
146-
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
147-
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
148-
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
149-
'This Month': [moment().startOf('month'), moment().endOf('month')],
150-
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
144+
if (startDate && endDate) {
145+
datepicker.value = `${startDate}/${endDate}`
151146
}
152-
}, function(start, end) {
153-
let formattedStart = start.format('YYYY-MM-DD');
154-
let formattedEnd = end.format('YYYY-MM-DD');
155-
dateLink.textContent = formattedStart + ' - ' + formattedEnd;
156-
updateURL(fieldName + "_after", fieldName + "_before", formattedStart, formattedEnd);
157-
});
158-
});
147+
})
148+
})
159149
</script>
160150
{% endif %}
161151
</nav>

hypha/apply/funds/templates/submissions/all.html

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
hx-target="#main"
4747
hx-push-url="true"
4848
hx-swap="outerHTML transition:true"
49+
name="querySubmissions"
4950
>
5051

5152
{% dropdown_menu title="Filters" heading="Filter submissions" %}
@@ -322,19 +323,21 @@
322323
x-show="!showSelectedSubmissions"
323324
class="flex flex-wrap gap-2 items-center menu-filters"
324325
>
325-
<div id="filtersubmitted" aria-label="Filter by Submitted" class="flex items-center" data-query="submitted">
326-
<button class="flex justify-between items-center py-1 w-full font-medium border cursor-pointer md:p-0 md:border-none text-base-content/80 ps-2 pe-2 md:pe-4 md:hover:bg-transparent md:hover:text-primary hover:bg-base-200">
327-
{% trans "Submitted" %}
328-
{% heroicon_mini "chevron-down" aria_hidden="true" width=18 height=18 class="hidden md:inline-block" %}
329-
</button>
330-
</div>
326+
{% dropdown_menu title="Submitted" heading="Filter by submitted date(s)" %}
327+
<calendar-range class="mx-auto cally" {% if selected_submitted_date %}value="{{selected_submitted_date}}"{% endif %} onchange="setDateFromPicker(this.value, 'submitted')">
328+
{% heroicon_micro "chevron-left" aria_label="Previous" slot="previous" aria_hidden=true size=18 %}
329+
{% heroicon_micro "chevron-right" aria_label="Next" slot="next" aria_hidden=true size=18 %}
330+
<calendar-month></calendar-month>
331+
</calendar-range>
332+
{% enddropdown_menu %}
331333

332-
<div id="filterupdated" aria-label="Filter by Updated" class="flex items-center" data-query="updated">
333-
<button class="flex justify-between items-center py-1 w-full font-medium border cursor-pointer md:p-0 md:border-none text-base-content/80 ps-2 pe-2 md:pe-4 md:hover:bg-transparent md:hover:text-primary hover:bg-base-200">
334-
{% trans "Updated" %}
335-
{% heroicon_mini "chevron-down" aria_hidden="true" width=18 height=18 class="hidden md:inline-block" %}
336-
</button>
337-
</div>
334+
{% dropdown_menu title="Updated" heading="Filter by updated date(s)" %}
335+
<calendar-range class="mx-auto cally" {% if selected_updated_date %}value="{{selected_updated_date}}"{% endif %} onchange="setDateFromPicker(this.value, 'updated')">
336+
{% heroicon_micro "chevron-left" aria_label="Previous" slot="previous" aria_hidden=true size=18 %}
337+
{% heroicon_micro "chevron-right" aria_label="Next" slot="next" aria_hidden=true size=18 %}
338+
<calendar-month></calendar-month>
339+
</calendar-range>
340+
{% enddropdown_menu %}
338341

339342
{% dropdown_menu title="Status" heading="Filter by current status" enable_search=True %}
340343
<ul
@@ -530,38 +533,22 @@ <h2 class='mb-2 text-2xl card-title'>{% trans "No results matched your search" %
530533
{% endspaceless %}{% endblock content %}
531534

532535
{% block extra_js %}
533-
<!-- Datetime picker-->
534-
<script src="{% static 'js/vendor/moment.min.js' %}"></script>
535-
<script src="{% static 'js/vendor/daterangepicker.min.js' %}"></script>
536536
<script>
537-
// Date Range Picker
538-
// ---------------------------------------------
539-
htmx.onLoad(function() {
540-
var start = moment().subtract(29, 'days');
541-
var end = moment();
542-
543-
// Add the picker for all elements that need it
544-
$.each(['#filterupdated', '#filtersubmitted'], (index, element) => {
545-
$(element).daterangepicker({
546-
startDate: start,
547-
endDate: end,
548-
ranges: {
549-
'{% trans "Today" %}': [moment(), moment()],
550-
'{% trans "Yesterday" %}': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
551-
'{% trans "Last 7 Days" %}': [moment().subtract(6, 'days'), moment()],
552-
'{% trans "Last 30 Days" %}': [moment().subtract(29, 'days'), moment()],
553-
'{% trans "This Month" %}': [moment().startOf('month'), moment().endOf('month')],
554-
'{% trans "Last Month" %}': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
555-
}
556-
});
557-
558-
var query = $(element).attr("data-query");
559-
560-
$(element).on('apply.daterangepicker', function(ev, picker) {
561-
$('#search-navbar').val(`${query}:>=${picker.startDate.format('YYYY-MM-DD')} ${query}:<=${picker.endDate.format('YYYY-MM-DD')}`);
562-
$('#search-navbar').closest('form').trigger('submit');
563-
});
564-
})
565-
});
537+
/**
538+
* Change the value of the query bar based on the provided date range and query param,
539+
* then submit the query
540+
*
541+
* @param {string} dateRange - the date range in the format of `YYYY-MM-DD/YYYY-MM-DD`
542+
* @param {string} query - the param to be used in the search bar. ie. `updated`
543+
*
544+
*/
545+
function setDateFromPicker(dateRange, query) {
546+
const searchBar = document.getElementById("search-navbar");
547+
let [startDate, endDate] = dateRange.split("/");
548+
549+
// Update searchbar value and submit the query
550+
searchBar.value = `${query}:>=${startDate} ${query}:<=${endDate}`
551+
document.querySubmissions.submit()
552+
}
566553
</script>
567554
{% endblock %}

hypha/apply/funds/views/all.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def submissions_all(
108108
)
109109
selected_sort = request.GET.get("sort")
110110
page = request.GET.get("page", 1)
111+
selected_updated_date = None
112+
selected_submitted_date = None
111113

112114
can_view_archives = permissions.can_view_archived_submissions(request.user)
113115
can_access_drafts = permissions.can_access_drafts(request.user)
@@ -143,12 +145,12 @@ def submissions_all(
143145
qs = qs.exclude_draft()
144146

145147
if "submitted" in search_filters:
146-
qs = apply_date_filter(
148+
qs, selected_submitted_date = apply_date_filter(
147149
qs=qs, field="submit_time", values=search_filters["submitted"]
148150
)
149151

150152
if "updated" in search_filters:
151-
qs = apply_date_filter(
153+
qs, selected_updated_date = apply_date_filter(
152154
qs=qs, field="last_update", values=search_filters["updated"]
153155
)
154156

@@ -323,6 +325,8 @@ def submissions_all(
323325
"selected_reviewers": selected_reviewers,
324326
"selected_meta_terms": selected_meta_terms,
325327
"selected_category_options": selected_category_options,
328+
"selected_updated_date": selected_updated_date,
329+
"selected_submitted_date": selected_submitted_date,
326330
"status_counts": status_counts,
327331
"sort_options": sort_options,
328332
"selected_sort": selected_sort,

hypha/apply/search/filters.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
import datetime as dt
2+
from typing import Tuple
23

3-
from django.db.models import Q
4+
from django.db.models import Q, QuerySet
45

56
from hypha.apply.search.query_parser import tokenize_date_filter_value
67

78

8-
def apply_date_filter(qs, field, values):
9-
"""Given a queryset, a field name, and a list of date strings, filter the queryset."""
9+
def apply_date_filter(qs, field, values) -> Tuple[QuerySet, str]:
10+
"""Given a queryset, a field name, and a list of date strings, filter the queryset.
11+
12+
Returns:
13+
tuple: the filtered queryset and the parsed date range in the format of `[YYYY-MM-DD start date]/[YYYY-MM-DD end date]`
14+
15+
"""
1016
q_obj = Q()
1117

18+
date_range = []
19+
1220
for date_str in values:
1321
tokens = tokenize_date_filter_value(date_str)
22+
date_range.append(f"{tokens[-3]}-{tokens[-2]:02}-{tokens[-1]:02}")
1423

1524
if q := date_filter_tokens_to_q_obj(tokens=tokens, field=field):
1625
q_obj &= q
1726
else:
1827
return qs.none()
1928

20-
return qs.filter(q_obj)
29+
return (qs.filter(q_obj), "/".join(date_range))
2130

2231

2332
def date_filter_tokens_to_q_obj(tokens: list, field: str) -> Q:

hypha/static_src/javascript/esm/cally-0-8-0.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hypha/static_src/javascript/esm/choices.js-11-1-0.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hypha/static_src/javascript/esm/github-filter-input-element-0-1-1.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)