diff --git a/hypha/apply/activity/context_processors.py b/hypha/apply/activity/context_processors.py index 0a9a62168a8a8a6c96be2c8c54a01a98cf63d085..975680a3c1a58d4bec894d1d395eb519410fe01c 100644 --- a/hypha/apply/activity/context_processors.py +++ b/hypha/apply/activity/context_processors.py @@ -5,5 +5,5 @@ def notification_context(request): context_data = dict() if hasattr(request, 'user'): if request.user.is_authenticated and request.user.is_apply_staff: - context_data['latest_notifications'] = Activity.objects.all().order_by('-timestamp')[:5] + context_data['latest_notifications'] = Activity.objects.latest().order_by('-timestamp')[:5] return context_data diff --git a/hypha/apply/activity/models.py b/hypha/apply/activity/models.py index 2b6c99ff21cfe71e350242cb2ac4637f180fc20a..99a4010044e866db751d13ca0cb5314fb4a382b3 100644 --- a/hypha/apply/activity/models.py +++ b/hypha/apply/activity/models.py @@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Case, Value, When from django.db.models.functions import Concat +from django.utils import timezone from .options import MESSAGES @@ -54,6 +55,9 @@ class ActivityQuerySet(BaseActivityQuerySet): def actions(self): return self.filter(type=ACTION) + def latest(self): + return self.filter(timestamp__gte=(timezone.now() - timezone.timedelta(days=30))) + class ActivityBaseManager(models.Manager): def create(self, **kwargs): diff --git a/hypha/apply/activity/templates/activity/notifications.html b/hypha/apply/activity/templates/activity/notifications.html index bd48329c06dcf9e49406004b24952455698efd51..b4b2c26c014be956f62a46c97c1977bd9b2a6bfd 100644 --- a/hypha/apply/activity/templates/activity/notifications.html +++ b/hypha/apply/activity/templates/activity/notifications.html @@ -6,8 +6,8 @@ <div class="admin-bar__inner"> <div class="admin-bar__inner--with-button"> <h1 class="gamma heading heading--no-margin heading--bold">{% trans "Notifications" %}</h1> - <form class="form notification__filters" method="get"> - {{ filter.form.as_p }} + <form class="form notifications__filters" method="get"> + {{ filter.form }} <button class="button button--primary" type="submit" value="Filter">{% trans "Filter" %}</button> </form> </div> diff --git a/hypha/apply/activity/views.py b/hypha/apply/activity/views.py index 09253b5bac50bb8bcafa88bfaa654796b2b2ade9..0af55e2b554adbd9aa64bce00b9415388a0cdf3c 100644 --- a/hypha/apply/activity/views.py +++ b/hypha/apply/activity/views.py @@ -70,7 +70,7 @@ class NotificationsView(ListView): def get_queryset(self): # List only last 30 days' activities - queryset = Activity.objects.filter(timestamp__gte=(timezone.now() - timezone.timedelta(days=30))) + queryset = Activity.objects.latest() self.filterset = self.filterset_class(self.request.GET, queryset=queryset) return self.filterset.qs.distinct().order_by('-timestamp') diff --git a/hypha/static_src/src/javascript/apply/notifications.js b/hypha/static_src/src/javascript/apply/notifications.js index f3e619abdca50ae927468c0848652fcf564eaebe..a24bcd16282a16723886992f228f7b78a8189d96 100644 --- a/hypha/static_src/src/javascript/apply/notifications.js +++ b/hypha/static_src/src/javascript/apply/notifications.js @@ -1,19 +1,21 @@ -function notificationToggle() { - 'use strict'; - document.getElementById('notificationDropdown').classList.toggle('show'); -} +(function () { -// Close the dropdown menu if the user clicks outside of it -window.onclick = function (event) { 'use strict'; - if (!event.target.matches('.dropbtn, .dropbtn *')) { - var dropdowns = document.getElementsByClassName('dropdown-content'); - var i; - for (i = 0; i < dropdowns.length; i++) { - var openDropdown = dropdowns[i]; - if (openDropdown.classList.contains('show')) { - notificationToggle(); + + // Open/close dropdown when users clicks the bell. + document.querySelector('.notifications__bell').addEventListener('click', function () { + document.querySelector('.notifications__content').classList.toggle('hidden'); + }); + + // Close the dropdown menu if the user clicks outside of it. + window.onclick = function (event) { + if (!event.target.matches('.notifications--dropdown, .notifications--dropdown *')) { + const dropdown = document.querySelector('.notifications__content'); + if (!dropdown.classList.contains('hidden')) { + dropdown.classList.add('hidden'); } } - } -}; + }; + + +})(); diff --git a/hypha/static_src/src/sass/apply/components/_activity-notifications.scss b/hypha/static_src/src/sass/apply/components/_activity-notifications.scss index b949d79350cb3e2d81a595ea7a9e32679a8d05c8..74cb4494bcd71ad167f56042bf97a1743009d773 100644 --- a/hypha/static_src/src/sass/apply/components/_activity-notifications.scss +++ b/hypha/static_src/src/sass/apply/components/_activity-notifications.scss @@ -1,55 +1,51 @@ -.dropbtn { - padding: 7px 12px; - cursor: pointer; -} - -.dropdown { - position: relative; - display: inline-block; -} +.notifications { + &--dropdown { + position: relative; + display: inline-block; + } -.dropdown-content { - display: none; - position: absolute; - right: 0; - font-size: 15px; - background-color: $color--light-grey; - min-width: 400px; - box-shadow: 1px 1px 6px 4px $color--light-mid-grey; - - p { - color: $color--black; - padding: 0 10px; - display: block; + &__bell { + padding: 7px 12px; + cursor: pointer; } -} -.dropdown-item { - border-bottom: .1px solid $color--dark-grey; -} + &__content { + position: absolute; + right: 1em; + padding: 1em; + margin-top: .5em; + background-color: $color--light-grey; + min-width: 400px; + box-shadow: 2px 2px 6px 1px $color--dark-grey; + } -.show-all { - text-align: center; -} + &__item { + padding-bottom: 1em; + border-bottom: 1px solid $color--dark-grey; + } -.show { - display: block; -} + &__more { + text-align: center; + font-weight: $weight--semibold; + } -.notification { &__filters { display: flex; align-items: center; padding: 4px; justify-content: space-between; - p { - font-weight: 520; - padding-right: 10px; + label { + font-weight: $weight--semibold; + padding-right: 1em; } select { padding-right: 1em; } + + .form__select { + margin-right: 1em; + } } } diff --git a/hypha/templates/base-apply.html b/hypha/templates/base-apply.html index f9706cdd2e500d8a7a3177f1df6e93ddf0946252..d3a4bf058f0868bac84054558a70d5cb01dc20bb 100644 --- a/hypha/templates/base-apply.html +++ b/hypha/templates/base-apply.html @@ -30,7 +30,7 @@ <script src="{% static 'js/jquery.min.js' %}"></script> <script src="{% static 'js/js.cookie.min.js' %}"></script> <script src="{% static 'js/main-top.js' %}"></script> - <script src="{% static 'js/apply/notifications.js' %}"></script> + <script defer src="{% static 'js/apply/notifications.js' %}"></script> {% if COOKIES_ACCEPTED and MATOMO_URL and MATOMO_SITEID %} {# we are only expecting strings, so make sure we escape the values #} <script> @@ -106,21 +106,21 @@ <div class="header__button-container"> {% if latest_notifications %} - <div class="dropdown"> - <a href="#" onclick="return notificationToggle()" class=" button--contains-icons dropbtn" aria-haspopup="activity" aria-expanded="false" role="button"> + <div class="notifications notifications--dropdown"> + <a href="#" class="button button--contains-icons notifications__bell" aria-label="{% trans "Notifications" %}" aria-haspopup="activity" aria-expanded="false" role="button"> <svg class="icon"><use xlink:href="#bell-icon"></use></svg> </a> - <div id="notificationDropdown" class="dropdown-content" role="activity"> - <p><b>Notifications</b></p> + <div class="notifications__content zeta hidden" role="activity"> + <h5>Notifications</h5> {% for notification in latest_notifications %} - <p class="dropdown-item"> - <span><b>{{ notification.source_content_type.name|source_type }}</b></span> + <p class="notifications__item"> + <strong>{{ notification.source_content_type.name|source_type }} </strong> <a href="{{ notification.source.get_absolute_url }}">{{ notification.source.title|capfirst|truncatechars:15 }}</a> : {{ notification.user }} {% ifequal notification.type 'comment' %}made a comment{% else %} {{ notification.message|safe }} {% if notification.related_object %}<a href="{{ notification.related_object.get_absolute_url }}">{{ notification.related_object|model_verbose_name }}</a>{% endif %}{% endifequal %} </p> {% endfor %} - <p class="show-all"><a href="{% url "activity:notifications" %}">Show All</a></p> + <p class="notifications__more"><a href="{% url "activity:notifications" %}">Show All</a></p> </div> </div> {% endif %}