diff --git a/application/Espo/Modules/Crm/Controllers/Activities.php b/application/Espo/Modules/Crm/Controllers/Activities.php
index 4213e428cd..4c19b4bd4d 100644
--- a/application/Espo/Modules/Crm/Controllers/Activities.php
+++ b/application/Espo/Modules/Crm/Controllers/Activities.php
@@ -28,7 +28,7 @@ use \Espo\Core\Exceptions\Error,
class Activities extends \Espo\Core\Controllers\Base
{
- public static $defaultAction = 'index';
+ public static $defaultAction = 'index';
public function actionListCalendarEvents($params, $data, $request)
{
@@ -48,6 +48,13 @@ class Activities extends \Espo\Core\Controllers\Base
return $service->getEvents($this->getUser()->id, $from, $to);
}
+ public function actionPopupNotifications()
+ {
+ $userId = $this->getUser()->id;
+
+ return $this->getService('Activities')->getPopupNotifications($userId);
+ }
+
public function actionList($params, $data, $request)
{
$name = $params['name'];
@@ -68,7 +75,7 @@ class Activities extends \Espo\Core\Controllers\Base
$scope = null;
if (!empty($where) && !empty($where['scope']) && $where['scope'] !== 'false') {
$scope = $where['scope'];
- }
+ }
$service = $this->getService('Activities');
diff --git a/application/Espo/Modules/Crm/Resources/metadata/app/popupNotifications.json b/application/Espo/Modules/Crm/Resources/metadata/app/popupNotifications.json
new file mode 100644
index 0000000000..63341c644a
--- /dev/null
+++ b/application/Espo/Modules/Crm/Resources/metadata/app/popupNotifications.json
@@ -0,0 +1,7 @@
+{
+ "event": {
+ "url": "Activities/action/popupNotifications",
+ "interval": 5,
+ "view": "Crm:Meeting.PopupNotification"
+ }
+}
diff --git a/application/Espo/Modules/Crm/Services/Activities.php b/application/Espo/Modules/Crm/Services/Activities.php
index aef76bad0f..7084cf7004 100644
--- a/application/Espo/Modules/Crm/Services/Activities.php
+++ b/application/Espo/Modules/Crm/Services/Activities.php
@@ -430,11 +430,23 @@ class Activities extends \Espo\Core\Services\Base
";
- $sth = $pdo->prepare($sql);
- $sth->execute();
+ $sth = $pdo->prepare($sql);
+ $sth->execute();
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
return $rows;
}
+
+ public function getPopupNotifications($userId)
+ {
+ return array(
+ array(
+ 'id' => '43276532423',
+ 'entityType' => 'Call',
+ 'dateStart' => '2014-12-05 14:10',
+ 'name' => 'Test Call'
+ )
+ );
+ }
}
diff --git a/frontend/client/modules/crm/res/templates/meeting/popup-notification.tpl b/frontend/client/modules/crm/res/templates/meeting/popup-notification.tpl
new file mode 100644
index 0000000000..086f7094e8
--- /dev/null
+++ b/frontend/client/modules/crm/res/templates/meeting/popup-notification.tpl
@@ -0,0 +1,19 @@
+{{#if closeButton}}
+×
+{{/if}}
+
').attr('id', 'popup-notifications-container').addClass('hidden').appendTo('body');
+ }
+
+ var popupNotificationsData = this.popupNotificationsData = this.getMetadata().get('app.popupNotifications') || {};
+ for (var name in popupNotificationsData) {
+ this.checkPopupNotifications(name);
+ }
},
showNotRead: function (count) {
@@ -59,7 +96,7 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
this.$badge.attr('title', '');
},
- checkUpdates: function () {
+ checkUpdates: function () {
$.ajax('Notification/action/notReadCount').done(function (count) {
if (count) {
this.showNotRead(count);
@@ -70,7 +107,65 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
this.timeout = setTimeout(function () {
this.checkUpdates();
- }.bind(this), this.updateFrequency * 1000)
+ }.bind(this), this.updateFrequency * 1000);
+ },
+
+ checkPopupNotifications: function (name) {
+ var data = this.popupNotificationsData[name] || {};
+ var url = data.url;
+ var interval = data.interval;
+
+ if (!url || !interval) return;
+
+ $.ajax(url).done(function (list) {
+ list.forEach(function (d) {
+ this.showPopupNotification(name, d);
+ }, this);
+ }.bind(this));
+
+ this.popoupTimeouts[name] = setTimeout(function () {
+ this.checkPopupNotifications(name);
+ }.bind(this), interval * 1000);
+ },
+
+ showPopupNotification: function (name, data) {
+ var view = this.popupNotificationsData[name].view;
+ if (!view) return;
+
+ var id = data.id || null;
+
+ if (id) {
+ id = name + '_' + id;
+ if (~this.shownNotificationIds.indexOf(id)) return;
+ if (~this.closedNotificationIds.indexOf(id)) return;
+ } else {
+ id = this.lastId++;
+ }
+
+ this.shownNotificationIds.push(id);
+
+ this.createView('popup-' + id, view, {
+ notificationData: data,
+ id: id
+ }, function (view) {
+ view.render();
+ this.$popupContainer.removeClass('hidden');
+ this.listenTo(view, 'remove', function () {
+ this.markPopupRemoved(id);
+ localStorage.setItem('closePopupNotificationId', id);
+ }, this);
+ }.bind(this));
+ },
+
+ markPopupRemoved: function (id) {
+ var index = this.shownNotificationIds.indexOf(id);
+ if (index > -1) {
+ this.shownNotificationIds.splice(index, 1);
+ }
+ if (this.shownNotificationIds.length == 0) {
+ this.$popupContainer.addClass('hidden');
+ }
+ this.closedNotificationIds.push(id);
},
showNotifications: function () {
@@ -82,12 +177,12 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
'z-index': 1001,
'right': 0,
'left': 'auto'
- });
+ });
$container.appendTo(this.$el.find('.notifications-panel-container'));
this.createView('panel', 'Notifications.Panel', {
- el: '#notifications-panel',
+ el: '#notifications-panel',
}, function (view) {
view.render();
this.listenTo(view, 'all-read', function () {
@@ -96,24 +191,24 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
}, this);
}.bind(this));
- $document = $(document);
- $document.on('mouseup.notification', function (e) {
- if (!$container.is(e.target) && $container.has(e.target).length === 0) {
+ $document = $(document);
+ $document.on('mouseup.notification', function (e) {
+ if (!$container.is(e.target) && $container.has(e.target).length === 0) {
this.closeNotifications();
- }
+ }
}.bind(this));
},
closeNotifications: function () {
$container = $('#notifications-panel');
- $('#notifications-panel').remove();
+ $('#notifications-panel').remove();
$document = $(document);
if (this.hasView('panel')) {
this.getView('panel').remove();
};
- $document.off('mouseup.notification');
- $container.remove();
+ $document.off('mouseup.notification');
+ $container.remove();
},
});
diff --git a/frontend/client/src/views/popup-notification.js b/frontend/client/src/views/popup-notification.js
new file mode 100644
index 0000000000..c3f594459b
--- /dev/null
+++ b/frontend/client/src/views/popup-notification.js
@@ -0,0 +1,84 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ ************************************************************************/
+
+Espo.define('Views.PopupNotification', 'View', function (Dep) {
+
+ return Dep.extend({
+
+ type: 'default',
+
+ style: 'default',
+
+ closeButton: true,
+
+ init: function () {
+ Dep.prototype.init.call(this);
+
+ var id = this.options.id;
+ var containerSelector = this.containerSelector = '#' + id;
+
+ this.on('render', function () {
+ $(containerSelector).remove();
+
+ var className = 'popup-notification-' + Espo.Utils.toDom(this.type);
+
+ $('
').attr('id', id)
+ .addClass('popup-notification')
+ .addClass(className)
+ .addClass('popup-notification-' + this.style)
+ .appendTo('#popup-notifications-container');
+
+ this.setElement(containerSelector);
+ }, this);
+
+ this.on('after:render', function () {
+ this.$el.find('[data-action="close"]').on('click', function () {
+ this.cancel();
+ }.bind(this));
+ }, this);
+
+ this.once('remove', function () {
+ $(containerSelector).remove();
+ });
+
+ this.notificationData = this.options.notificationData;
+ },
+
+ data: function () {
+ return {
+ closeButton: this.closeButton,
+ notificationData: this.notificationData
+ };
+ },
+
+ onCancel: function () {
+ },
+
+ cancel: function () {
+ this.onCancel();
+ this.trigger('cancel');
+ this.remove();
+ }
+
+
+ });
+});
+
diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less
index 93d53d2dd3..adb3411258 100644
--- a/frontend/less/espo/custom.less
+++ b/frontend/less/espo/custom.less
@@ -677,3 +677,77 @@ img.avatar.avatar-link {
.complext-text a {
word-wrap: break-word;
}
+
+#popup-notifications-container {
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ right: 0px;
+ bottom: 0px;
+ position: fixed;
+ height: auto;
+ z-index: 1000;
+
+ float: right;
+}
+
+.popup-notification {
+ width: 400px;
+ padding: 14px;
+ background-color: @panel-bg;
+ border: 1px solid @main-gray;
+ margin-top: 10px;
+}
+
+.popup-notification a.close{
+ margin-top: -8px;
+ margin-right: -3px;
+}
+
+.popup-notification-default {
+ border-color: @gray-light;
+}
+
+.popup-notification-danger {
+ border-color: @brand-danger;
+}
+
+.popup-notification-warning {
+ border-color: @brand-warning;
+}
+
+.popup-notification-primary {
+ border-color: @brand-primary;
+}
+
+.popup-notification-success {
+ border-color: @brand-success;
+}
+
+.popup-notification-info {
+ border-color: @brand-info;
+}
+
+.popup-notification h4 {
+ margin-top: 0px;
+}
+
+.popup-notification-default h4 {
+ color: @gray-light;
+}
+
+.popup-notification-danger h4 {
+ color: @brand-danger;
+}
+
+.popup-notification-warning h4 {
+ color: @brand-warning;
+}
+
+.popup-notification-primary h4 {
+ color: @brand-primary;
+}
+
+.popup-notification-success h4 {
+ color: @brand-success;
+}