mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-26 09:42:46 +08:00
Update notifications flyout [SCI-10735]
This commit is contained in:
parent
100c84732d
commit
c71366acc5
9 changed files with 132 additions and 29 deletions
|
@ -30,7 +30,7 @@
|
|||
flex-direction: column;
|
||||
height: calc(100vh - 8rem);
|
||||
padding: 1.5rem;
|
||||
width: 400px;
|
||||
width: 600px;
|
||||
|
||||
.sci--navigation--notificaitons-flyout-title {
|
||||
@include font-h2;
|
||||
|
|
|
@ -5,11 +5,18 @@ class UserNotificationsController < ApplicationController
|
|||
|
||||
def index
|
||||
page = (params.dig(:page, :number) || 1).to_i
|
||||
notifications = load_notifications.page(page).per(Constants::INFINITE_SCROLL_LIMIT)
|
||||
notifications = load_notifications
|
||||
|
||||
case params[:tab]
|
||||
when 'all'
|
||||
notifications = notifications.where.not(read_at: nil)
|
||||
when 'unread'
|
||||
notifications = notifications.where(read_at: nil)
|
||||
end
|
||||
|
||||
notifications = notifications.page(page).per(Constants::INFINITE_SCROLL_LIMIT)
|
||||
|
||||
render json: notifications, each_serializer: NotificationSerializer
|
||||
|
||||
notifications.mark_as_read!
|
||||
end
|
||||
|
||||
def unseen_counter
|
||||
|
@ -18,6 +25,17 @@ class UserNotificationsController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
def mark_all_read
|
||||
load_notifications.mark_as_read!
|
||||
render json: { success: true }
|
||||
end
|
||||
|
||||
def toggle_read
|
||||
notification = current_user.notifications.find(params[:id])
|
||||
notification.update(read_at: (params[:mark_as_read] ? DateTime.now : nil))
|
||||
render json: notification, serializer: NotificationSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_notifications
|
||||
|
@ -25,5 +43,4 @@ class UserNotificationsController < ApplicationController
|
|||
.in_app
|
||||
.order(created_at: :desc)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
<template>
|
||||
<div class="sci-navigation--notificaitons-flyout-notification">
|
||||
<div class="sci-navigation--notificaitons-flyout-notification-date">
|
||||
{{ notification.attributes.created_at }}
|
||||
<div class="flex item-center">
|
||||
<div class="sci-navigation--notificaitons-flyout-notification-date">
|
||||
{{ notification.attributes.created_at }}
|
||||
</div>
|
||||
<div class="ml-auto cursor-pointer" @click="toggleRead()">
|
||||
<div v-if="!notification.attributes.checked" class="w-2.5 h-2.5 bg-sn-coral rounded-full cursor-pointer"></div>
|
||||
<div v-else class="w-2.5 h-2.5 border-2 border-sn-grey rounded-full border-solid cursor-pointer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sci-navigation--notificaitons-flyout-notification-title"
|
||||
v-html="notification.attributes.title"
|
||||
:data-seen="notification.attributes.checked"></div>
|
||||
<div v-html="notification.attributes.message" class="sci-navigation--notificaitons-flyout-notification-message"></div>
|
||||
<a :href="lastBreadcrumbUrl" @click="toggleRead(true)" class="hover:no-underline text-black hover:text-black">
|
||||
<div class="sci-navigation--notificaitons-flyout-notification-title"
|
||||
v-html="notification.attributes.title"
|
||||
:data-seen="notification.attributes.checked"></div>
|
||||
<div v-html="notification.attributes.message" class="sci-navigation--notificaitons-flyout-notification-message"></div>
|
||||
</a>
|
||||
<div v-if="notification.attributes.breadcrumbs" class="flex items-center flex-wrap gap-0.5">
|
||||
<template v-for="(breadcrumb, index) in notification.attributes.breadcrumbs" :key="index">
|
||||
<div class="flex items-center gap-0.5">
|
||||
|
@ -19,23 +27,33 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'NotificationItem',
|
||||
props: {
|
||||
notification: Object
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
switch (this.notification.attributes.type_of) {
|
||||
case 'deliver':
|
||||
return 'fas fa-truck';
|
||||
case 'assignment':
|
||||
return 'fas fa-list-alt';
|
||||
case 'recent_changes':
|
||||
return 'fas fa-list-alt';
|
||||
case 'deliver_error':
|
||||
return 'sn-icon sn-icon-alert-warning';
|
||||
lastBreadcrumbUrl() {
|
||||
if (!this.notification.attributes.breadcrumbs) {
|
||||
return null;
|
||||
}
|
||||
return this.notification.attributes.breadcrumbs[this.notification.attributes.breadcrumbs.length - 1]?.url;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleRead(check = false) {
|
||||
const params = {};
|
||||
if (!this.notification.attributes.checked || check) {
|
||||
params.mark_as_read = true;
|
||||
}
|
||||
|
||||
axios.post(this.notification.attributes.toggle_read_url, params)
|
||||
.then((response) => {
|
||||
const notification = response.data.data;
|
||||
this.$emit('updateNotification', notification);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,17 +6,45 @@
|
|||
{{ i18n.t('nav.settings') }}
|
||||
</a>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
@click="changeTab('all')"
|
||||
class="px-4 py-2 text-sn-grey cursor-pointer border-solid border-0 border-b-4 border-transparent"
|
||||
:class="{'!text-sn-black border-sn-blue': activeTab === 'all'}"
|
||||
>
|
||||
{{ i18n.t('nav.notifications.all') }}
|
||||
</div>
|
||||
<div
|
||||
@click="changeTab('unread')"
|
||||
class="px-4 py-2 text-sn-grey cursor-pointer border-solid border-0 border-b-4 border-transparent"
|
||||
:class="{'!text-sn-black border-sn-blue': activeTab === 'unread'}"
|
||||
>
|
||||
{{ i18n.t('nav.notifications.unread') }}
|
||||
</div>
|
||||
<div
|
||||
@click="changeTab('read')"
|
||||
class="px-4 py-2 text-sn-grey cursor-pointer border-solid border-0 border-b-4 border-transparent"
|
||||
:class="{'!text-sn-black border-sn-blue': activeTab === 'read'}"
|
||||
>
|
||||
{{ i18n.t('nav.notifications.read') }}
|
||||
</div>
|
||||
<div class="py-4 ml-auto cursor-pointer" @click="markAllNotificationsAsRead">
|
||||
{{ i18n.t('nav.notifications.read_all') }}
|
||||
</div>
|
||||
</div>
|
||||
<hr class="!mt-0.5">
|
||||
<perfect-scrollbar @wheel="preventPropagation" ref="scrollContainer" class="sci--navigation--notificaitons-flyout-notifications">
|
||||
<div class="sci-navigation--notificaitons-flyout-subtitle" v-if="todayNotifications.length">
|
||||
{{ i18n.t('nav.notifications.today') }}
|
||||
</div>
|
||||
<NotificationItem v-for="notification in todayNotifications" :key="notification.type_of + '-' + notification.id"
|
||||
@updateNotification="updateNotification"
|
||||
:notification="notification" />
|
||||
<div class="sci-navigation--notificaitons-flyout-subtitle" v-if="olderNotifications.length">
|
||||
{{ i18n.t('nav.notifications.older') }}
|
||||
</div>
|
||||
<NotificationItem v-for="notification in olderNotifications" :key="notification.type_of + '-' + notification.id"
|
||||
@updateNotification="updateNotification"
|
||||
:notification="notification" />
|
||||
<div class="next-page-loader">
|
||||
<img src="/images/medium/loading.svg" v-if="loadingPage" />
|
||||
|
@ -37,6 +65,7 @@ export default {
|
|||
},
|
||||
props: {
|
||||
notificationsUrl: String,
|
||||
markAllNotificationsUrl: String,
|
||||
unseenNotificationsCount: Number,
|
||||
preferencesUrl: String
|
||||
},
|
||||
|
@ -45,11 +74,14 @@ export default {
|
|||
notifications: [],
|
||||
nextPageUrl: null,
|
||||
scrollBar: null,
|
||||
loadingPage: false
|
||||
activeTab: 'all',
|
||||
loadingPage: false,
|
||||
firstPageUrl: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.nextPageUrl = this.notificationsUrl;
|
||||
this.firstPageUrl = this.notificationsUrl;
|
||||
this.loadNotifications();
|
||||
},
|
||||
mounted() {
|
||||
|
@ -76,6 +108,27 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
changeTab(tab) {
|
||||
this.activeTab = tab;
|
||||
this.notifications = [];
|
||||
this.nextPageUrl = this.firstPageUrl;
|
||||
this.loadNotifications();
|
||||
},
|
||||
markAllNotificationsAsRead() {
|
||||
axios.post(this.markAllNotificationsUrl)
|
||||
.then(() => {
|
||||
this.notifications = this.notifications.map((n) => {
|
||||
n.attributes.checked = true;
|
||||
return n;
|
||||
});
|
||||
this.$emit('update:unseenNotificationsCount');
|
||||
});
|
||||
},
|
||||
updateNotification(notification) {
|
||||
const index = this.notifications.findIndex((n) => n.id === notification.id);
|
||||
this.notifications.splice(index, 1, notification);
|
||||
this.$emit('update:unseenNotificationsCount');
|
||||
},
|
||||
preventPropagation(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
@ -85,7 +138,7 @@ export default {
|
|||
|
||||
this.loadingPage = true;
|
||||
|
||||
axios.get(this.nextPageUrl)
|
||||
axios.get(this.nextPageUrl, { params: { tab: this.activeTab } })
|
||||
.then((response) => {
|
||||
this.notifications = this.notifications.concat(response.data.data);
|
||||
this.nextPageUrl = response.data.links.next;
|
||||
|
|
|
@ -45,8 +45,9 @@
|
|||
<NotificationsFlyout
|
||||
:preferencesUrl="user.preferences_url"
|
||||
:notificationsUrl="notificationsUrl"
|
||||
:markAllNotificationsUrl="markAllNotificationsUrl"
|
||||
:unseenNotificationsCount="unseenNotificationsCount"
|
||||
@update:unseenNotificationsCount="checkUnseenNotifications()"
|
||||
@update:unseenNotificationsCount="checkUnseenNotifications(false)"
|
||||
@close="$refs.notificationDropdown.$refs.field.click();"/>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
|
@ -92,6 +93,7 @@ export default {
|
|||
props: {
|
||||
url: String,
|
||||
notificationsUrl: String,
|
||||
markAllNotificationsUrl: String,
|
||||
unseenNotificationsUrl: String,
|
||||
quickSearchUrl: String,
|
||||
teamsUrl: String,
|
||||
|
@ -119,7 +121,7 @@ export default {
|
|||
|
||||
$(document).on('turbolinks:load', () => {
|
||||
this.notificationsOpened = false;
|
||||
this.checkUnseenNotifications();
|
||||
this.checkUnseenNotifications(false);
|
||||
this.refreshCurrentTeam();
|
||||
this.hideSearch = !!document.getElementById('GlobalSearch');
|
||||
this.globalSearchKey += 1;
|
||||
|
@ -177,11 +179,13 @@ export default {
|
|||
searchValue(e) {
|
||||
window.open(`${this.searchUrl}?q=${e.target.value}`, '_self');
|
||||
},
|
||||
checkUnseenNotifications() {
|
||||
checkUnseenNotifications(repeat = true) {
|
||||
clearTimeout(this.unseenNotificationsTimeout);
|
||||
$.get(this.unseenNotificationsUrl, (result) => {
|
||||
this.unseenNotificationsCount = result.unseen;
|
||||
this.unseenNotificationsTimeout = setTimeout(this.checkUnseenNotifications, 30000);
|
||||
if (repeat) {
|
||||
this.unseenNotificationsTimeout = setTimeout(this.checkUnseenNotifications, 30000);
|
||||
}
|
||||
});
|
||||
},
|
||||
refreshCurrentTeam() {
|
||||
|
|
|
@ -4,7 +4,7 @@ class NotificationSerializer < ActiveModel::Serializer
|
|||
include Rails.application.routes.url_helpers
|
||||
include BreadcrumbsHelper
|
||||
|
||||
attributes :id, :title, :message, :created_at, :read_at, :type, :breadcrumbs, :checked, :today
|
||||
attributes :id, :title, :message, :created_at, :read_at, :type, :breadcrumbs, :checked, :today, :toggle_read_url
|
||||
|
||||
def title
|
||||
object.to_notification.title
|
||||
|
@ -31,4 +31,7 @@ class NotificationSerializer < ActiveModel::Serializer
|
|||
object.read_at.present?
|
||||
end
|
||||
|
||||
def toggle_read_url
|
||||
toggle_read_user_notification_path(object)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<top-menu-container
|
||||
url="<%= top_menu_navigations_path %>"
|
||||
notifications-url="<%= user_notifications_path %>"
|
||||
mark-all-notifications-url="<%= mark_all_read_user_notifications_path %>"
|
||||
quick-search-url="<%= quick_search_path %>"
|
||||
unseen-notifications-url="<%= unseen_counter_user_notifications_path %>"
|
||||
teams-url="<%= visible_teams_teams_path %>"
|
||||
|
|
|
@ -310,6 +310,9 @@ en:
|
|||
academy_tooltip: "SciNote Academy"
|
||||
notifications:
|
||||
title: "Notifications"
|
||||
read: "Read"
|
||||
unread: "Unread"
|
||||
read_all: "Read all"
|
||||
all: "All"
|
||||
message: "Messages"
|
||||
system: "System"
|
||||
|
|
|
@ -168,6 +168,10 @@ Rails.application.routes.draw do
|
|||
collection do
|
||||
get :filter_groups
|
||||
get :unseen_counter
|
||||
post :mark_all_read
|
||||
end
|
||||
member do
|
||||
post :toggle_read
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue