mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-28 09:24:24 +01:00
(quality) replace Angular-controlled notifications list with react code
This commit is contained in:
parent
9b69aad9df
commit
7a83a38c68
@ -25,7 +25,7 @@ class API::NotificationsController < API::ApiController
|
||||
end
|
||||
@totals = {
|
||||
total: current_user.notifications.delivered_in_system(current_user).count,
|
||||
unread: current_user.notifications.where(is_read: false).count
|
||||
unread: current_user.notifications.delivered_in_system(current_user).where(is_read: false).count
|
||||
}
|
||||
render :index
|
||||
end
|
||||
@ -33,6 +33,7 @@ class API::NotificationsController < API::ApiController
|
||||
def last_unread
|
||||
loop do
|
||||
@notifications = current_user.notifications
|
||||
.delivered_in_system(current_user)
|
||||
.includes(:attached_object)
|
||||
.where(is_read: false)
|
||||
.limit(3)
|
||||
@ -42,7 +43,7 @@ class API::NotificationsController < API::ApiController
|
||||
end
|
||||
@totals = {
|
||||
total: current_user.notifications.delivered_in_system(current_user).count,
|
||||
unread: current_user.notifications.where(is_read: false).count
|
||||
unread: current_user.notifications.delivered_in_system(current_user).where(is_read: false).count
|
||||
}
|
||||
render :index
|
||||
end
|
||||
@ -53,7 +54,7 @@ class API::NotificationsController < API::ApiController
|
||||
.order('created_at DESC')
|
||||
@totals = {
|
||||
total: current_user.notifications.delivered_in_system(current_user).count,
|
||||
unread: current_user.notifications.where(is_read: false).count
|
||||
unread: current_user.notifications.delivered_in_system(current_user).where(is_read: false).count
|
||||
}
|
||||
render :index
|
||||
end
|
||||
|
20
app/frontend/src/javascript/api/notification.ts
Normal file
20
app/frontend/src/javascript/api/notification.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { NotificationsIndex, Notification } from '../models/notification';
|
||||
|
||||
export default class NotificationAPI {
|
||||
static async index (page?: number): Promise<NotificationsIndex> {
|
||||
const withPage = page ? `?page=${page}` : '';
|
||||
const res: AxiosResponse<NotificationsIndex> = await apiClient.get(`/api/notifications${withPage}`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async update (updatedNotification: Notification): Promise<Notification> {
|
||||
const res: AxiosResponse<Notification> = await apiClient.patch(`/api/notifications/${updatedNotification.id}`, { notification: updatedNotification });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async update_all (): Promise<void> {
|
||||
await apiClient.patch('/api/notifications');
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { Loader } from '../base/loader';
|
||||
import { Notification } from '../../models/notification';
|
||||
import FormatLib from '../../lib/format';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface NotificationInlineProps {
|
||||
notification: Notification,
|
||||
onUpdate?: (Notification) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays one notification
|
||||
*/
|
||||
const NotificationInline: React.FC<NotificationInlineProps> = ({ notification, onUpdate }) => {
|
||||
const { t } = useTranslation('logged');
|
||||
const createdAt = new Date(notification.created_at);
|
||||
|
||||
// Call a parent component method to update the notification
|
||||
const update = () => onUpdate(notification);
|
||||
|
||||
return (
|
||||
<div className="notification-inline">
|
||||
<div className="date">{ FormatLib.date(createdAt) } { FormatLib.time(createdAt) }</div>
|
||||
<div className="message" dangerouslySetInnerHTML={{ __html: notification.message.description }}/>
|
||||
{onUpdate && <FabButton onClick={update} className="is-secondary">{ t('app.logged.notification_inline.mark_as_read') }</FabButton>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationInlineWrapper: React.FC<NotificationInlineProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<NotificationInline {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotificationInlineWrapper as NotificationInline };
|
@ -0,0 +1,57 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { FabTabs } from '../base/fab-tabs';
|
||||
import { NotificationsList } from './notifications-list';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MemberAPI from '../../api/member';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface NotificationsCenterProps {
|
||||
onError: (message: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* This Admin component groups two tabs : a list of notifications and the notifications settings
|
||||
*/
|
||||
export const NotificationsCenter: React.FC<NotificationsCenterProps> = ({ onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
MemberAPI.current()
|
||||
.then(data => {
|
||||
if (data.role === 'admin') setIsAdmin(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAdmin && <FabTabs defaultTab='notifications-list' tabs={[
|
||||
{
|
||||
id: 'notifications_settings',
|
||||
title: t('app.admin.notifications_center.notifications_settings'),
|
||||
content: 'to do notifications_settings'
|
||||
},
|
||||
{
|
||||
id: 'notifications-list',
|
||||
title: t('app.admin.notifications_center.notifications_list'),
|
||||
content: <NotificationsList onError={onError}/>
|
||||
}
|
||||
]} />}
|
||||
{!isAdmin && <NotificationsList onError={onError}/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationsCenterWrapper: React.FC<NotificationsCenterProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<NotificationsCenter {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('notificationsCenter', react2angular(NotificationsCenterWrapper, ['onError']));
|
@ -0,0 +1,109 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Loader } from '../base/loader';
|
||||
import { Notification, NotificationsTotals } from '../../models/notification';
|
||||
import NotificationAPI from '../../api/notification';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NotificationInline } from './notification-inline';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
|
||||
interface NotificationsListProps {
|
||||
onError: (message: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the list of notifications
|
||||
*/
|
||||
const NotificationsList: React.FC<NotificationsListProps> = ({ onError }) => {
|
||||
const { t } = useTranslation('logged');
|
||||
|
||||
const [notifications, setNotifications] = useState<Array<Notification>>([]);
|
||||
const [totals, setTotals] = useState<NotificationsTotals>({ total: 0, unread: 0 });
|
||||
const [page, setPage] = useState<number>(1);
|
||||
|
||||
const newNotifications = notifications.filter(notification => notification.is_read === false);
|
||||
const pastNotifications = notifications.filter(notification => notification.is_read === true);
|
||||
|
||||
// Fetch Notification and Notification Totals from API
|
||||
const fetchNotifications = () => {
|
||||
NotificationAPI.index()
|
||||
.then(data => {
|
||||
setTotals(data.totals);
|
||||
setNotifications(data.notifications);
|
||||
})
|
||||
.catch(onError);
|
||||
};
|
||||
|
||||
// Fetch Notifications and Notification Totals on component mount
|
||||
useEffect(() => {
|
||||
fetchNotifications();
|
||||
}, []);
|
||||
|
||||
// Call Notifications API to set one notification as read, and fetch the updated Notifications & Totals
|
||||
const markAsRead = async (notification: Notification) => {
|
||||
await NotificationAPI.update(notification);
|
||||
fetchNotifications();
|
||||
};
|
||||
|
||||
// Call Notifications API to set all notifications as read, and fetch the updated Notifications & Totals
|
||||
const markAllAsRead = async () => {
|
||||
await NotificationAPI.update_all();
|
||||
fetchNotifications();
|
||||
};
|
||||
|
||||
// Calculate if they are notifications that are not yet displayed
|
||||
// If true, allows user to display more notifications
|
||||
const isMoreNotifications = (totals.total - notifications.length) > 0;
|
||||
|
||||
// Call API to Load More Notifications
|
||||
const loadMoreNotifications = () => {
|
||||
if (isMoreNotifications) {
|
||||
const nextPage = page + 1;
|
||||
NotificationAPI.index(nextPage)
|
||||
.then(data => {
|
||||
setNotifications(prevState => [...prevState, ...data.notifications]);
|
||||
setPage(nextPage);
|
||||
})
|
||||
.catch(onError);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="notifications-list">
|
||||
<header className="notifications-header">
|
||||
<h2 className="title">{t('app.logged.notifications_list.notifications')}</h2>
|
||||
{totals.unread > 0 &&
|
||||
<FabButton onClick={markAllAsRead} className="is-main">
|
||||
{ t('app.logged.notifications_list.mark_all_as_read')} ({totals.unread})
|
||||
</FabButton>}
|
||||
</header>
|
||||
{totals.unread === 0 && <p>{ t('app.logged.notifications_list.no_new_notifications') }</p>}
|
||||
<div>
|
||||
{newNotifications.map(notification => <NotificationInline key={notification.id} notification={notification} onUpdate={markAsRead} />)}
|
||||
</div>
|
||||
{pastNotifications.length > 0 &&
|
||||
<div className='archives'>
|
||||
<h3 className="title">{ t('app.logged.notifications_list.archives') }</h3>
|
||||
{pastNotifications.length === 0
|
||||
? <p>{ t('app.logged.notifications_list.no_archived_notifications') }</p>
|
||||
: pastNotifications.map(notification => <NotificationInline key={notification.id} notification={notification} />)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{isMoreNotifications &&
|
||||
<FabButton className="is-black notifications-loader" onClick={loadMoreNotifications}>
|
||||
{ t('app.logged.notifications_list.load_the_next_notifications') }
|
||||
</FabButton>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationsListWrapper: React.FC<NotificationsListProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<NotificationsList {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotificationsListWrapper as NotificationsList };
|
@ -14,100 +14,15 @@
|
||||
|
||||
/**
|
||||
* Controller used in notifications page
|
||||
* inherits $scope.$parent.notifications (global notifications state) from ApplicationController
|
||||
*/
|
||||
Application.Controllers.controller('NotificationsController', ['$scope', 'Notification', function ($scope, Notification) {
|
||||
Application.Controllers.controller('NotificationsController', ['$scope', 'growl', function ($scope, growl) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// Array containg the archived notifications (already read)
|
||||
$scope.notificationsRead = [];
|
||||
|
||||
// Array containg the new notifications (not read)
|
||||
$scope.notificationsUnread = [];
|
||||
|
||||
// Total number of notifications for the current user
|
||||
$scope.total = 0;
|
||||
|
||||
// Total number of unread notifications for the current user
|
||||
$scope.totalUnread = 0;
|
||||
|
||||
// By default, the pagination mode is activated to limit the page size
|
||||
$scope.paginateActive = true;
|
||||
|
||||
// The currently displayed page number
|
||||
$scope.page = 1;
|
||||
|
||||
/**
|
||||
* Mark the provided notification as read, updating its status on the server and moving it
|
||||
* to the already read notifications list.
|
||||
* @param notification {{id:number}} the notification to mark as read
|
||||
* @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
|
||||
*/
|
||||
$scope.markAsRead = function (notification, e) {
|
||||
e.preventDefault();
|
||||
return Notification.update({ id: notification.id }, {
|
||||
id: notification.id,
|
||||
is_read: true
|
||||
}
|
||||
, function (updatedNotif) {
|
||||
// remove notif from unreads
|
||||
const index = $scope.notificationsUnread.indexOf(notification);
|
||||
$scope.notificationsUnread.splice(index, 1);
|
||||
// add update notif to read
|
||||
$scope.notificationsRead.push(updatedNotif);
|
||||
// update counters
|
||||
$scope.$parent.notifications.unread -= 1;
|
||||
return $scope.totalUnread -= 1;
|
||||
});
|
||||
* Shows an error message forwarded from a child react component
|
||||
*/
|
||||
$scope.onError = function (message) {
|
||||
growl.error(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark every unread notifications as read and move them for the unread list to to read array.
|
||||
*/
|
||||
$scope.markAllAsRead = () =>
|
||||
Notification.update({}
|
||||
, function () { // success
|
||||
// add notifs to read
|
||||
angular.forEach($scope.notificationsUnread, function (n) {
|
||||
n.is_read = true;
|
||||
return $scope.notificationsRead.push(n);
|
||||
});
|
||||
// clear unread
|
||||
$scope.notificationsUnread = [];
|
||||
// update counters
|
||||
$scope.$parent.notifications.unread = 0;
|
||||
return $scope.totalUnread = 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* Request the server to retrieve the next notifications and add them
|
||||
* to their corresponding notifications list (read or unread).
|
||||
*/
|
||||
$scope.addMoreNotifications = function () {
|
||||
Notification.query({ page: $scope.page }, function (notifications) {
|
||||
$scope.total = notifications.totals.total;
|
||||
$scope.totalUnread = notifications.totals.unread;
|
||||
angular.forEach(notifications.notifications, function (notif) {
|
||||
if (notif.is_read) {
|
||||
return $scope.notificationsRead.push(notif);
|
||||
} else {
|
||||
return $scope.notificationsUnread.push(notif);
|
||||
}
|
||||
});
|
||||
return $scope.paginateActive = (notifications.totals.total > ($scope.notificationsRead.length + $scope.notificationsUnread.length));
|
||||
});
|
||||
|
||||
return $scope.page += 1;
|
||||
};
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
* Kind of constructor: these actions will be realized first when the controller is loaded
|
||||
*/
|
||||
const initialize = () => $scope.addMoreNotifications();
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
]);
|
||||
|
83
app/frontend/src/javascript/models/notification-type.ts
Normal file
83
app/frontend/src/javascript/models/notification-type.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { ApiFilter } from '../models/api';
|
||||
|
||||
export interface NotificationType {
|
||||
id: number,
|
||||
name: typeof notificationTypeNames[number],
|
||||
category: string,
|
||||
is_configurable: boolean
|
||||
}
|
||||
|
||||
export interface NotificationTypeIndexFilter extends ApiFilter {
|
||||
is_configurable?: boolean
|
||||
}
|
||||
|
||||
export const notificationTypeNames = [
|
||||
'notify_admin_when_project_published',
|
||||
'notify_project_collaborator_to_valid',
|
||||
'notify_project_author_when_collaborator_valid',
|
||||
'notify_user_training_valid',
|
||||
'notify_member_subscribed_plan',
|
||||
'notify_member_create_reservation',
|
||||
'notify_member_subscribed_plan_is_changed',
|
||||
'notify_admin_member_create_reservation',
|
||||
'notify_member_slot_is_modified',
|
||||
'notify_admin_slot_is_modified',
|
||||
'notify_admin_when_user_is_created',
|
||||
'notify_admin_subscribed_plan',
|
||||
'notify_user_when_invoice_ready',
|
||||
'notify_member_subscription_will_expire_in_7_days',
|
||||
'notify_member_subscription_is_expired',
|
||||
'notify_admin_subscription_will_expire_in_7_days',
|
||||
'notify_admin_subscription_is_expired',
|
||||
'notify_admin_subscription_canceled',
|
||||
'notify_member_subscription_canceled',
|
||||
'notify_user_when_avoir_ready',
|
||||
'notify_member_slot_is_canceled',
|
||||
'notify_admin_slot_is_canceled',
|
||||
'notify_partner_subscribed_plan',
|
||||
'notify_member_subscription_extended',
|
||||
'notify_admin_subscription_extended',
|
||||
'notify_admin_user_group_changed',
|
||||
'notify_user_user_group_changed',
|
||||
'notify_admin_when_user_is_imported',
|
||||
'notify_user_profile_complete',
|
||||
'notify_user_auth_migration',
|
||||
'notify_admin_user_merged',
|
||||
'notify_admin_profile_complete',
|
||||
'notify_admin_abuse_reported',
|
||||
'notify_admin_invoicing_changed',
|
||||
'notify_user_wallet_is_credited',
|
||||
'notify_admin_user_wallet_is_credited',
|
||||
'notify_admin_export_complete',
|
||||
'notify_member_about_coupon',
|
||||
'notify_member_reservation_reminder',
|
||||
'notify_admin_free_disk_space',
|
||||
'notify_admin_close_period_reminder',
|
||||
'notify_admin_archive_complete',
|
||||
'notify_privacy_policy_changed',
|
||||
'notify_admin_import_complete',
|
||||
'notify_admin_refund_created',
|
||||
'notify_admins_role_update',
|
||||
'notify_user_role_update',
|
||||
'notify_admin_objects_stripe_sync',
|
||||
'notify_user_when_payment_schedule_ready',
|
||||
'notify_admin_payment_schedule_failed',
|
||||
'notify_member_payment_schedule_failed',
|
||||
'notify_admin_payment_schedule_check_deadline',
|
||||
'notify_admin_payment_schedule_transfer_deadline',
|
||||
'notify_admin_payment_schedule_error',
|
||||
'notify_member_payment_schedule_error',
|
||||
'notify_admin_payment_schedule_gateway_canceled',
|
||||
'notify_member_payment_schedule_gateway_canceled',
|
||||
'notify_admin_user_proof_of_identity_files_created',
|
||||
'notify_admin_user_proof_of_identity_files_updated',
|
||||
'notify_user_is_validated',
|
||||
'notify_user_is_invalidated',
|
||||
'notify_user_proof_of_identity_refusal',
|
||||
'notify_admin_user_proof_of_identity_refusal',
|
||||
'notify_user_order_is_ready',
|
||||
'notify_user_order_is_canceled',
|
||||
'notify_user_order_is_refunded',
|
||||
'notify_admin_low_stock_threshold',
|
||||
'notify_admin_training_auto_cancelled'
|
||||
] as const;
|
27
app/frontend/src/javascript/models/notification.ts
Normal file
27
app/frontend/src/javascript/models/notification.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { TDateISO } from '../typings/date-iso';
|
||||
|
||||
import { notificationTypeNames } from './notification-type';
|
||||
|
||||
export type NotificationName = typeof notificationTypeNames[number];
|
||||
|
||||
export interface Notification {
|
||||
id: number,
|
||||
notification_type_id: number,
|
||||
notification_type: NotificationName,
|
||||
created_at: TDateISO,
|
||||
is_read: boolean,
|
||||
message: {
|
||||
title: string,
|
||||
description: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface NotificationsTotals {
|
||||
total: number,
|
||||
unread: number
|
||||
}
|
||||
|
||||
export interface NotificationsIndex {
|
||||
totals: NotificationsTotals,
|
||||
notifications: Array<Notification>
|
||||
}
|
@ -74,6 +74,8 @@
|
||||
@import "modules/machines/machines-list";
|
||||
@import "modules/machines/machines-settings";
|
||||
@import "modules/machines/required-training-modal";
|
||||
@import "modules/notifications/notifications-list";
|
||||
@import "modules/notifications/notification-line";
|
||||
@import "modules/payment-schedule/payment-schedule-dashboard";
|
||||
@import "modules/payment-schedule/payment-schedule-summary";
|
||||
@import "modules/payment-schedule/payment-schedules-list";
|
||||
|
@ -0,0 +1,30 @@
|
||||
.notification-inline {
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--gray-soft-darkest);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.date {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin: 0 4rem;
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media (max-width: 1054px) {
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
.message {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
.notifications-list {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.notifications-header { @include header; }
|
||||
|
||||
.archives {
|
||||
margin: 2rem 0;
|
||||
padding: 1rem 3rem;
|
||||
border-radius: 5px;
|
||||
background-color: var(--gray-soft-light);
|
||||
|
||||
.title {
|
||||
@include title-base;
|
||||
color: var(--gray-hard-darkest) !important;
|
||||
}
|
||||
|
||||
}
|
||||
.notifications-loader {
|
||||
width: fit-content;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
@ -14,80 +14,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="m-lg">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t-sm m-b" ng-click="markAllAsRead()" ng-disabled="totalUnread == 0">{{ 'app.logged.notifications.mark_all_as_read' | translate }} ({{totalUnread}})</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:10%"></th>
|
||||
<th style="width:20%" translate>{{ 'app.logged.notifications.date' }}</th>
|
||||
<th style="width:70%" translate>{{ 'app.logged.notifications.notif_title' }}</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="notification in notificationsUnread" ng-if="notificationsUnread.length > 0">
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" ng-click="markAsRead(notification, $event)">
|
||||
<i class="fa fa-check"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td>{{ notification.created_at | amDateFormat:'LLL' }}</td>
|
||||
<td ng-bind-html="notification.message.description"></td>
|
||||
|
||||
</tr>
|
||||
<tr ng-if="notificationsUnread.length == 0">
|
||||
<td colspan="3" translate>{{ 'app.logged.notifications.no_new_notifications' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div ng-hide="notificationsRead.length == 0 && notificationsUnread.length < total">
|
||||
<h5 translate>{{ 'app.logged.notifications.archives' }}</h5>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:10%"></th>
|
||||
<th style="width:20%"></th>
|
||||
<th style="width:70%"></th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
|
||||
|
||||
<tr class="read" ng-repeat="n in notificationsRead | orderBy:'created_at':true" ng-if="notificationsRead.length > 0">
|
||||
<td>
|
||||
</td>
|
||||
<td>{{ n.created_at | amDateFormat:'LLL' }}</td>
|
||||
<td ng-bind-html="n.message.description"></td>
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
<tr ng-if="notificationsRead.length == 0">
|
||||
<td colspan="3" translate>{{ 'app.logged.notifications.no_archived_notifications' }}</td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-default" ng-click="addMoreNotifications()" ng-if="paginateActive" translate>{{ 'app.logged.notifications.load_the_next_notifications' }}</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<notifications-center on-error="onError"></notification-center>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -6,4 +6,6 @@ class NotificationType < ApplicationRecord
|
||||
has_many :notification_preferences, dependent: :destroy
|
||||
|
||||
validates :name, uniqueness: true, presence: true
|
||||
validates :category, presence: true
|
||||
validates :is_configurable, inclusion: { in: [true, false] }
|
||||
end
|
||||
|
@ -2412,3 +2412,6 @@ en:
|
||||
cta_switch: "Display a button"
|
||||
cta_label: "Button label"
|
||||
cta_url: "Button link"
|
||||
notifications_center:
|
||||
notifications_list: "All notifications"
|
||||
notifications_settings: "My notifications preferences"
|
||||
|
@ -251,6 +251,8 @@ en:
|
||||
i_change: "I change"
|
||||
notifications:
|
||||
notifications_center: "Notifications center"
|
||||
notifications_list:
|
||||
notifications: "All notifications"
|
||||
mark_all_as_read: "Mark all as read"
|
||||
date: "Date"
|
||||
notif_title: "Title"
|
||||
@ -258,3 +260,5 @@ en:
|
||||
archives: "Archives"
|
||||
no_archived_notifications: "No archived notifications."
|
||||
load_the_next_notifications: "Load the next notifications..."
|
||||
notification_line:
|
||||
mark_as_read: "Mark as read"
|
||||
|
@ -21,7 +21,7 @@ en:
|
||||
token_if_link_problem: "If you experience issues with the link, you can enter the following code at your first connection attempt:"
|
||||
notifications_mailer:
|
||||
notify_user_user_group_changed:
|
||||
subject: "Your have changed group"
|
||||
subject: "You have changed group"
|
||||
body:
|
||||
warning: "You have changed group. Inspections can be conducted at the lab to verify the legitimacy of this change."
|
||||
user_invalidated: "Your account was invalidated, please upload your new supporting documents to validate your account."
|
||||
|
@ -6,80 +6,89 @@
|
||||
# This migration is linked to the abandon of the NotifyWith gem. Notification Types
|
||||
# will now be store in database and we will manage ourself the Notification system.
|
||||
class CreateNotificationTypes < ActiveRecord::Migration[5.2]
|
||||
NAMES = %w[
|
||||
notify_admin_when_project_published
|
||||
notify_project_collaborator_to_valid
|
||||
notify_project_author_when_collaborator_valid
|
||||
notify_user_training_valid
|
||||
notify_member_subscribed_plan
|
||||
notify_member_create_reservation
|
||||
notify_member_subscribed_plan_is_changed
|
||||
notify_admin_member_create_reservation
|
||||
notify_member_slot_is_modified
|
||||
notify_admin_slot_is_modified
|
||||
notify_admin_when_user_is_created
|
||||
notify_admin_subscribed_plan
|
||||
notify_user_when_invoice_ready
|
||||
notify_member_subscription_will_expire_in_7_days
|
||||
notify_member_subscription_is_expired
|
||||
notify_admin_subscription_will_expire_in_7_days
|
||||
notify_admin_subscription_is_expired
|
||||
notify_admin_subscription_canceled
|
||||
notify_member_subscription_canceled
|
||||
notify_user_when_avoir_ready
|
||||
notify_member_slot_is_canceled
|
||||
notify_admin_slot_is_canceled
|
||||
notify_partner_subscribed_plan
|
||||
notify_member_subscription_extended
|
||||
notify_admin_subscription_extended
|
||||
notify_admin_user_group_changed
|
||||
notify_user_user_group_changed
|
||||
notify_admin_when_user_is_imported
|
||||
notify_user_profile_complete
|
||||
notify_user_auth_migration
|
||||
notify_admin_user_merged
|
||||
notify_admin_profile_complete
|
||||
notify_admin_abuse_reported
|
||||
notify_admin_invoicing_changed
|
||||
notify_user_wallet_is_credited
|
||||
notify_admin_user_wallet_is_credited
|
||||
notify_admin_export_complete
|
||||
notify_member_about_coupon
|
||||
notify_member_reservation_reminder
|
||||
notify_admin_free_disk_space
|
||||
notify_admin_close_period_reminder
|
||||
notify_admin_archive_complete
|
||||
notify_privacy_policy_changed
|
||||
notify_admin_import_complete
|
||||
notify_admin_refund_created
|
||||
notify_admins_role_update
|
||||
notify_user_role_update
|
||||
notify_admin_objects_stripe_sync
|
||||
notify_user_when_payment_schedule_ready
|
||||
notify_admin_payment_schedule_failed
|
||||
notify_member_payment_schedule_failed
|
||||
notify_admin_payment_schedule_check_deadline
|
||||
notify_admin_payment_schedule_transfer_deadline
|
||||
notify_admin_payment_schedule_error
|
||||
notify_member_payment_schedule_error
|
||||
notify_admin_payment_schedule_gateway_canceled
|
||||
notify_member_payment_schedule_gateway_canceled
|
||||
notify_admin_user_proof_of_identity_files_created
|
||||
notify_admin_user_proof_of_identity_files_updated
|
||||
notify_user_is_validated
|
||||
notify_user_is_invalidated
|
||||
notify_user_proof_of_identity_refusal
|
||||
notify_admin_user_proof_of_identity_refusal
|
||||
notify_user_order_is_ready
|
||||
notify_user_order_is_canceled
|
||||
notify_user_order_is_refunded
|
||||
notify_admin_low_stock_threshold
|
||||
notify_admin_training_auto_cancelled
|
||||
# Index start at 1. This is required due to previous functionning of the NotifyWith gem
|
||||
NOTIFICATIONS_TYPES = [
|
||||
{ id: 1, name: 'notify_admin_when_project_published', category: 'projects', is_configurable: true },
|
||||
{ id: 2, name: 'notify_project_collaborator_to_valid', category: 'projects', is_configurable: true },
|
||||
{ id: 3, name: 'notify_project_author_when_collaborator_valid', category: 'projects', is_configurable: true },
|
||||
{ id: 4, name: 'notify_user_training_valid', category: 'trainings', is_configurable: false },
|
||||
{ id: 5, name: 'notify_member_subscribed_plan', category: 'subscriptions', is_configurable: false },
|
||||
{ id: 6, name: 'notify_member_create_reservation', category: 'agenda', is_configurable: false },
|
||||
{ id: 7, name: 'notify_member_subscribed_plan_is_changed', category: 'deprecated', is_configurable: false },
|
||||
{ id: 8, name: 'notify_admin_member_create_reservation', category: 'agenda', is_configurable: true },
|
||||
{ id: 9, name: 'notify_member_slot_is_modified', category: 'agenda', is_configurable: false },
|
||||
{ id: 10, name: 'notify_admin_slot_is_modified', category: 'agenda', is_configurable: true },
|
||||
|
||||
{ id: 11, name: 'notify_admin_when_user_is_created', category: 'users_accounts', is_configurable: true },
|
||||
{ id: 12, name: 'notify_admin_subscribed_plan', category: 'subscriptions', is_configurable: true },
|
||||
{ id: 13, name: 'notify_user_when_invoice_ready', category: 'payments', is_configurable: true },
|
||||
{ id: 14, name: 'notify_member_subscription_will_expire_in_7_days', category: 'subscriptions', is_configurable: false },
|
||||
{ id: 15, name: 'notify_member_subscription_is_expired', category: 'subscriptions', is_configurable: false },
|
||||
{ id: 16, name: 'notify_admin_subscription_will_expire_in_7_days', category: 'subscriptions', is_configurable: true },
|
||||
{ id: 17, name: 'notify_admin_subscription_is_expired', category: 'subscriptions', is_configurable: true },
|
||||
{ id: 18, name: 'notify_admin_subscription_canceled', category: 'subscriptions', is_configurable: true },
|
||||
{ id: 19, name: 'notify_member_subscription_canceled', category: 'subscriptions', is_configurable: false },
|
||||
{ id: 20, name: 'notify_user_when_avoir_ready', category: 'wallet', is_configurable: false },
|
||||
|
||||
{ id: 21, name: 'notify_member_slot_is_canceled', category: 'agenda', is_configurable: false },
|
||||
{ id: 22, name: 'notify_admin_slot_is_canceled', category: 'agenda', is_configurable: true },
|
||||
{ id: 23, name: 'notify_partner_subscribed_plan', category: 'subscriptions', is_configurable: false },
|
||||
{ id: 24, name: 'notify_member_subscription_extended', category: 'subscriptions', is_configurable: false },
|
||||
{ id: 25, name: 'notify_admin_subscription_extended', category: 'subscriptions', is_configurable: true },
|
||||
{ id: 26, name: 'notify_admin_user_group_changed', category: 'users_accounts', is_configurable: true },
|
||||
{ id: 27, name: 'notify_user_user_group_changed', category: 'users_accounts', is_configurable: false },
|
||||
{ id: 28, name: 'notify_admin_when_user_is_imported', category: 'users_accounts', is_configurable: true },
|
||||
{ id: 29, name: 'notify_user_profile_complete', category: 'users_accounts', is_configurable: false },
|
||||
{ id: 30, name: 'notify_user_auth_migration', category: 'user', is_configurable: false },
|
||||
|
||||
{ id: 31, name: 'notify_admin_user_merged', category: 'users_accounts', is_configurable: true },
|
||||
{ id: 32, name: 'notify_admin_profile_complete', category: 'users_accounts', is_configurable: true },
|
||||
{ id: 33, name: 'notify_admin_abuse_reported', category: 'projects', is_configurable: true },
|
||||
{ id: 34, name: 'notify_admin_invoicing_changed', category: 'deprecated', is_configurable: false },
|
||||
{ id: 35, name: 'notify_user_wallet_is_credited', category: 'wallet', is_configurable: false },
|
||||
{ id: 36, name: 'notify_admin_user_wallet_is_credited', category: 'wallet', is_configurable: true },
|
||||
{ id: 37, name: 'notify_admin_export_complete', category: 'exports', is_configurable: false },
|
||||
{ id: 38, name: 'notify_member_about_coupon', category: 'agenda', is_configurable: false },
|
||||
{ id: 39, name: 'notify_member_reservation_reminder', category: 'agenda', is_configurable: false },
|
||||
|
||||
{ id: 40, name: 'notify_admin_free_disk_space', category: 'app_management', is_configurable: false },
|
||||
{ id: 41, name: 'notify_admin_close_period_reminder', category: 'accountings', is_configurable: true },
|
||||
{ id: 42, name: 'notify_admin_archive_complete', category: 'accountings', is_configurable: true },
|
||||
{ id: 43, name: 'notify_privacy_policy_changed', category: 'app_management', is_configurable: false },
|
||||
{ id: 44, name: 'notify_admin_import_complete', category: 'app_management', is_configurable: false },
|
||||
{ id: 45, name: 'notify_admin_refund_created', category: 'wallet', is_configurable: true },
|
||||
{ id: 46, name: 'notify_admins_role_update', category: 'users_accounts', is_configurable: true },
|
||||
{ id: 47, name: 'notify_user_role_update', category: 'users_accounts', is_configurable: false },
|
||||
{ id: 48, name: 'notify_admin_objects_stripe_sync', category: 'payments', is_configurable: false },
|
||||
{ id: 49, name: 'notify_user_when_payment_schedule_ready', category: 'payments', is_configurable: false },
|
||||
|
||||
{ id: 50, name: 'notify_admin_payment_schedule_failed', category: 'payments', is_configurable: true },
|
||||
{ id: 51, name: 'notify_member_payment_schedule_failed', category: 'payments', is_configurable: false },
|
||||
{ id: 52, name: 'notify_admin_payment_schedule_check_deadline', category: 'payments', is_configurable: true },
|
||||
{ id: 53, name: 'notify_admin_payment_schedule_transfer_deadline', category: 'payments', is_configurable: true },
|
||||
{ id: 54, name: 'notify_admin_payment_schedule_error', category: 'payments', is_configurable: true },
|
||||
{ id: 55, name: 'notify_member_payment_schedule_error', category: 'payments', is_configurable: false },
|
||||
{ id: 56, name: 'notify_admin_payment_schedule_gateway_canceled', category: 'payments', is_configurable: true },
|
||||
{ id: 57, name: 'notify_member_payment_schedule_gateway_canceled', category: 'payments', is_configurable: false },
|
||||
{ id: 58, name: 'notify_admin_user_proof_of_identity_files_created', category: 'proof_of_identity', is_configurable: true },
|
||||
{ id: 59, name: 'notify_admin_user_proof_of_identity_files_updated', category: 'proof_of_identity', is_configurable: true },
|
||||
|
||||
{ id: 60, name: 'notify_user_is_validated', category: 'users_accounts', is_configurable: false },
|
||||
{ id: 61, name: 'notify_user_is_invalidated', category: 'users_accounts', is_configurable: false },
|
||||
{ id: 62, name: 'notify_user_proof_of_identity_refusal', category: 'proof_of_identity', is_configurable: false },
|
||||
{ id: 63, name: 'notify_admin_user_proof_of_identity_refusal', category: 'proof_of_identity', is_configurable: true },
|
||||
{ id: 64, name: 'notify_user_order_is_ready', category: 'shop', is_configurable: true },
|
||||
{ id: 65, name: 'notify_user_order_is_canceled', category: 'shop', is_configurable: true },
|
||||
{ id: 66, name: 'notify_user_order_is_refunded', category: 'shop', is_configurable: true },
|
||||
{ id: 67, name: 'notify_admin_low_stock_threshold', category: 'shop', is_configurable: true },
|
||||
{ id: 68, name: 'notify_admin_training_auto_cancelled', category: 'trainings', is_configurable: true }
|
||||
].freeze
|
||||
|
||||
def up
|
||||
create_table :notification_types do |t|
|
||||
t.string :name, null: false
|
||||
t.string :category, null: false
|
||||
t.boolean :is_configurable, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
@ -87,9 +96,13 @@ class CreateNotificationTypes < ActiveRecord::Migration[5.2]
|
||||
add_index :notification_types, :name, unique: true
|
||||
|
||||
# Records previous notification types
|
||||
# Index start at 1. This is required due to previous functionning of the NotifyWith gem
|
||||
NAMES.each.with_index(1) do |type, index|
|
||||
NotificationType.create!(id: index, name: type)
|
||||
NOTIFICATIONS_TYPES.each do |notification_type|
|
||||
NotificationType.create!(
|
||||
id: notification_type[:id],
|
||||
name: notification_type[:name],
|
||||
category: notification_type[:category],
|
||||
is_configurable: notification_type[:is_configurable]
|
||||
)
|
||||
end
|
||||
|
||||
last_id = NotificationType.order(:id).last.id
|
||||
|
Loading…
Reference in New Issue
Block a user