Merge branch '472-cmb-release' of gitlab.com:jankstudio/resq into 460-cmb-data-migration-for-carfix-data

This commit is contained in:
Korina Cordero 2020-09-01 10:01:36 +00:00
commit 885d1911a2
13 changed files with 649 additions and 277 deletions

View file

@ -0,0 +1,9 @@
notification_ajax_list:
path: /ajax/notifications
controller: App\Controller\NotificationController::ajaxList
methods: [GET]
notification_ajax_update:
path: /ajax/notifications
controller: App\Controller\NotificationController::ajaxUpdate
methods: [POST]

View file

@ -221,6 +221,12 @@ services:
event: 'postPersist'
entity: 'App\Entity\JobOrder'
App\Service\NotificationManager:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$redis_mqtt_key: "mqtt_events"
$em: "@doctrine.orm.entity_manager"
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"

View file

@ -231,30 +231,38 @@ class DashboardMap {
);
}
putRiderAvailableMarker(id, lat, lng, name) {
this.putMarkerWithLabel(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_available,
this.layer_groups.rider_available,
this.options.rider_popup_url,
name
);
putRiderAvailableMarker(id, lat, lng) {
var my = this;
my.getRiderName(id, function(name) {
my.putMarkerWithLabel(
id,
lat,
lng,
my.rider_markers,
my.options.icons.rider_available,
my.layer_groups.rider_available,
my.options.rider_popup_url,
name
);
});
}
putRiderActiveJOMarker(id, lat, lng, name) {
this.putMarkerWithLabel(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_active_jo,
this.layer_groups.rider_active_jo,
this.options.rider_popup_url,
name
);
putRiderActiveJOMarker(id, lat, lng) {
var my = this;
my.getRiderName(id, function(name) {
my.putMarkerWithLabel(
id,
lat,
lng,
my.rider_markers,
my.options.icons.rider_active_jo,
my.layer_groups.rider_active_jo,
my.options.rider_popup_url,
name
);
});
}
removeRiderMarker(id) {
@ -305,41 +313,40 @@ class DashboardMap {
var lng = data.longitude;
var name = '';
if (my.rider_names.hasOwnProperty(id)) {
name = my.rider_names[id];
if (data.has_jo)
my.putRiderActiveJOMarker(id, lat, lng, name);
else
my.putRiderAvailableMarker(id, lat, lng, name)
} else {
getRiderName(id, my.options.rider_name_url, function(name) {
my.rider_names[id] = name;
if (data.has_jo)
my.putRiderActiveJOMarker(id, lat, lng, name);
else
my.putRiderAvailableMarker(id, lat, lng, name)
});
}
if (data.has_jo)
my.putRiderActiveJOMarker(id, lat, lng);
else
my.putRiderAvailableMarker(id, lat, lng);
});
// console.log(rider_markers);
});
}
}
function getRiderName(id, url, callback) {
var name = '';
var rider_url = url.replace('[id]', id);
getRiderName(id, callback) {
var name = '';
var rider_url = this.options.rider_name_url.replace('[id]', id);
$.ajax({
method: "GET",
url: rider_url
}).done(function(response) {
name = response.rider_name;
callback(name);
});
var my = this;
console.log('getting rider name for rider ' + id);
// check if we have it in cache
if (this.rider_names.hasOwnProperty(id)) {
name = this.rider_names[id];
callback(name);
} else {
// ajax call to get it
$.ajax({
method: "GET",
url: rider_url
}).done(function(response) {
name = response.rider_name;
// set name in cache
my.rider_names[id] = name;
callback(name);
});
}
}
}

View file

@ -142,6 +142,7 @@ class MapEventHandler {
}
} else {
console.log('rider not in availability check');
display_marker = false;
}
// TODO: cache rider availability (available vs active jo) status and check before displaying icon

View file

@ -0,0 +1,111 @@
class NotificationHandler {
constructor(options) {
this.options = options;
}
clearAll() {
// clear notification count
document.getElementById('notif-count').innerHTML = '';
// remove notifications
document.getElementById('notif-body').innerHTML = '';
}
loadAll() {
console.log('loading notifications');
// ajax load
var self = this;
var notif_update_url = this.options['notif_ajax_update_url'];
var xhr = new XMLHttpRequest();
xhr.open('GET', this.options['notif_ajax_url']);
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
var notifs = data.notifications;
// update notification unread count
var count_html = data.unread_count;
document.getElementById('notif-count').innerHTML = count_html;
// do we have any notifications?
if (notifs.length <= 0)
return;
// add actual notifications
var notif_body = document.getElementById('notif-body');
var notif_index = 0;
notifs.forEach(function(notif) {
var notif_date = moment(notif.date).fromNow();
var notif_html = '<div class="m-list-timeline__item">';
notif_html += '<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>';
notif_html += '<span class="m-list-timeline__text">';
notif_html += '<a href="">' + notif.text + '</a>'
notif_html += '</span>';
notif_html += '<span class="m-list-timeline__time">';
notif_html += notif_date;
notif_html += '</span>';
notif_html += '</div>';
notif_body.insertAdjacentHTML('beforeend', notif_html);
document.getElementsByClassName('m-list-timeline__item')[notif_index].addEventListener('click', function(e) {
e.preventDefault();
$.ajax({
method: "POST",
url: notif_update_url,
data: {id: notif.id}
}).done(function(response) {
window.location.href = notif.link;
});
});
notif_index++;
});
}
};
xhr.send();
}
listen(user_id, host, port, use_ssl = false) {
var d = new Date();
var client_id = "dash-" + user_id + "-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
this.mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = {
useSSL: use_ssl,
timeout: 3,
invocationContext: this,
onSuccess: this.onConnect.bind(this)
}
this.mqtt.onMessageArrived = this.onMessage.bind(this);
this.mqtt.connect(options);
}
onConnect(icontext) {
console.log('notification mqtt connected');
var my = icontext.invocationContext;
// subscribe to general notifications
my.mqtt.subscribe('user/0/notification');
}
onMessage(msg) {
console.log('notification event received');
// we don't care about messasge, we update
this.clearAll();
this.loadAll();
}
getIcon(type_id) {
if (type_id in this.options['icons']) {
return this.options['icons'][type_id];
}
return this.options['default_icon'];
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Doctrine\ORM\EntityManagerInterface;
use Catalyst\MenuBundle\Annotation\Menu;
use DateTime;
use DateInterval;
use App\Entity\Notification;
class NotificationController extends AbstractController
{
// TODO: security
public function ajaxList(EntityManagerInterface $em)
{
/*
$date = new DateTime();
$date->sub(new DateInterval('PT10M'));
$notifs = [
[
'id' => 1,
'type' => 'jo_new',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Sample incoming job order',
'link' => '#',
], [
'id' => 2,
'type' => 'rider_accept',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Sample rider has accepted job order',
'link' => '#',
], [
'id' => 3,
'type' => 'jo_cancel',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Customer has cancelled job order.',
'link' => '#',
], [
'id' => 3,
'type' => 'rider_reject',
'date' => $date->format('Y-m-d\TH:i:s.000P'),
'text' => 'Rider has rejected job order. Job order needs to be reassigned.',
'link' => '#',
],
];
$sample_data = [
'count' => 4,
'notifications' => $notifs,
];
*/
$notifs = $em->getRepository(Notification::class)->findBy(['user_id' => 0]);
$notif_data = [];
$unread_count = 0;
foreach ($notifs as $notif)
{
if (!($notif->isRead()))
$unread_count++;
$notif_data[] = [
'id' => $notif->getID(),
'type' => 'jo_new',
'is_read' => $notif->isRead(),
'is_fresh' => $notif->isFresh(),
'date' => $notif->getDateCreate()->format('Y-m-d\TH:i:s.000P'),
'text' => $notif->getMessage(),
'link' => $notif->getURL(),
];
}
$sample_data = [
'count' => count($notif_data),
'unread_count' => $unread_count,
'notifications' => $notif_data,
];
$res = new JsonResponse($sample_data);
return $res;
}
// TODO: security
public function ajaxUpdate(EntityManagerInterface $em, Request $req)
{
$notif_id = $req->request->get('id');
error_log($notif_id);
$notif = $em->getRepository(Notification::class)->find($notif_id);
if ($notif != null)
{
// TODO: fresh is if unread and still within x hours
// but for now fresh and unread are both the same
$notif->setIsRead(true);
$notif->setIsFresh(false);
$em->persist($notif);
$em->flush();
}
$res = new JsonResponse();
return $res;
}
}

148
src/Entity/Notification.php Normal file
View file

@ -0,0 +1,148 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="notification", indexes={@ORM\Index(columns={"user_id"})})
*/
class Notification
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// date / time created
/**
* @ORM\Column(type="datetime")
*/
protected $date_create;
// index by user for fast fetching
// user id (don't link, don't need to)
/**
* @ORM\Column(type="integer")
*/
protected $user_id;
// icon
/**
* @ORM\Column(type="string", length=25)
*/
protected $icon;
// message text
/**
* @ORM\Column(type="string", length=300)
*/
protected $message;
/**
* @ORM\Column(type="string", length=180)
*/
protected $url;
// has user read the notification
/**
* @ORM\Column(type="boolean")
*/
protected $flag_read;
// is this still a fresh notification for the user
/**
* @ORM\Column(type="boolean")
*/
protected $flag_fresh;
public function __construct()
{
$this->date_create = new DateTime();
$this->flag_read = false;
$this->flag_fresh = true;
}
public function getID()
{
return $this->id;
}
public function getDateCreate()
{
return $this->date_create;
}
public function setUserID($user_id)
{
$this->user_id = $user_id;
return $this;
}
public function getUserID()
{
return $this->user_id;
}
public function setIcon($icon)
{
$this->icon = $icon;
return $this;
}
public function getIcon()
{
return $this->icon;
}
public function setMessage($message)
{
$this->message = $message;
return $this;
}
public function getMessage()
{
return $this->message;
}
public function setURL($url)
{
$this->url = $url;
return $this;
}
public function getURL()
{
return $this->url;
}
public function setIsRead($bool = true)
{
$this->flag_read = $bool;
return $this;
}
public function isRead()
{
return $this->flag_read;
}
public function setIsFresh($bool = true)
{
$this->flag_fresh = $bool;
return $this;
}
public function isFresh()
{
return $this->flag_fresh;
}
}

View file

@ -449,6 +449,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$jo = $em->getRepository(JobOrder::class)->find($id);
$old_jo_status = null;
$old_rider = null;
if (empty($jo))
{
// new job order
@ -456,7 +457,8 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
}
else
{
//$old_rider = $jo->getRider();
// need to get old values of rider and status to see if we need to change JO status or not
$old_rider = $jo->getRider();
$old_jo_status = $jo->getStatus();
}
@ -639,10 +641,25 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// and JO is already in_transit or in_progress?
// retain old jo status if it's an update JO
if ($old_jo_status != null)
$jo->setStatus($old_jo_status);
else
// check old rider if it is also a reassignment
// old_rider should be null if JO has been rejected
if (($old_rider == null) && ($old_jo_status == null))
$jo->setStatus(JOStatus::ASSIGNED);
else
{
error_log('not a new JO');
$new_rider = $jo->getRider();
if ($new_rider != $old_rider)
{
// reassignment
$jo->setStatus(JOStatus::ASSIGNED);
}
else
{
if ($old_jo_status != null)
$jo->setStatus($old_jo_status);
}
}
// check if user is null, meaning call to create came from API
if ($user != null)
@ -702,8 +719,8 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$em->flush();
// check if JO has been reassigned
//if ($old_rider != $rider)
if ($old_jo_status != $jo->getStatus())
if ($old_rider != $jo->getRider())
//if ($old_jo_status != $jo->getStatus())
{
error_log('JO has been reassigned');
// TODO: refactor later

View file

@ -0,0 +1,48 @@
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\RedisClientProvider;
use App\Entity\Notification;
class NotificationManager
{
protected $redis;
protected $redis_mqtt_key;
protected $em;
const NOTIF_KEY = 'user/{user_id}/notification';
public function __construct(RedisClientProvider $redis_prov, EntityManagerInterface $em, $redis_mqtt_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->redis_mqtt_key = $redis_mqtt_key;
$this->em = $em;
}
// set user_id to 0 for all
public function sendNotification($user_id, $msg, $url)
{
// send mqtt
$chan = $this->getChannel($user_id);
$data = $chan . '|' . $msg;
$this->redis->lpush($this->redis_mqtt_key, $data);
// create notif
$notif = new Notification();
$notif->setUserID($user_id)
->setIcon('')
->setMessage($msg)
->setURL($url);
// save to db
$this->em->persist($notif);
$this->em->flush();
}
protected function getChannel($user_id)
{
return str_replace('{user_id}', $user_id, self::NOTIF_KEY );
}
}

View file

@ -5,6 +5,7 @@ namespace App\Service\RiderAPIHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use App\Ramcar\CMBServiceType;
use App\Ramcar\TradeInType;
@ -23,6 +24,7 @@ use App\Service\WarrantyHandler;
use App\Service\JobOrderHandlerInterface;
use App\Service\InvoiceGeneratorInterface;
use App\Service\RiderTracker;
use App\Service\NotificationManager;
use App\Entity\RiderSession;
use App\Entity\Rider;
@ -53,13 +55,16 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
protected $ic;
protected $session;
protected $upload_dir;
protected $nm;
protected $router;
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis,
EncoderFactoryInterface $ef, RiderCache $rcache,
string $country_code, MQTTClient $mclient,
WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler,
InvoiceGeneratorInterface $ic, string $upload_dir,
RiderTracker $rider_tracker)
RiderTracker $rider_tracker, NotificationManager $nm,
UrlGeneratorInterface $router)
{
$this->em = $em;
$this->redis = $redis;
@ -72,6 +77,8 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->ic = $ic;
$this->upload_dir = $upload_dir;
$this->rider_tracker = $rider_tracker;
$this->nm = $nm;
$this->router = $router;
// one device = one session, since we have control over the devices
// when a rider logs in, we just change the rider assigned to the device
@ -208,7 +215,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$rider_id = $rider->getID();
// cache rider location (default to hub)
// TODO: figure out longitude / latitude default
$this->rcache->addActiveRider($rider_id, 0, 0);
// $this->rcache->addActiveRider($rider_id, 0, 0);
// TODO: log rider logging in
@ -278,7 +285,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$rider->setActive(false);
// remove from cache
$this->rcache->removeActiveRider($rider->getID());
// $this->rcache->removeActiveRider($rider->getID());
// remove rider from session
$this->session->setRider(null);
@ -311,6 +318,11 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->em->flush();
// cache rider location (default to hub)
// TODO: figure out longitude / latitude default
$rider_id = $rider->getID();
$this->rcache->addActiveRider($rider_id, 0, 0);
// send mqtt event to put rider on map
// get rider coordinates from redis
$coord = $this->rider_tracker->getRiderLocation($rider->getID());
@ -360,6 +372,9 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->em->flush();
// remove from cache
$this->rcache->removeActiveRider($rider->getID());
// send mqtt event to remove rider from map
$channel = 'rider/' . $rider->getID() . '/availability';
$payload = [
@ -1069,6 +1084,10 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->em->flush();
// notification
$notif_url = $this->router->generate('jo_onestep_edit_form', ['id' => $jo->getID()]);
$this->nm->sendNotification(0, 'Job order has been cancelled by rider.', $notif_url);
return $data;
}
@ -1154,6 +1173,9 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->mclient->publish($channel, $rider_status);
$notif_url = $this->router->generate('jo_onestep_edit_form', ['id' => $jo->getID()]);
$this->nm->sendNotification(0, 'Job order has been rejected by rider.', $notif_url);
return $data;
}

View file

@ -2,6 +2,8 @@
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\RedisClientProvider;
use App\Entity\Rider;
@ -10,12 +12,14 @@ class RiderCache
protected $redis;
protected $loc_key;
protected $status_key;
protected $em;
public function __construct(RedisClientProvider $redis_prov, $loc_key, $status_key)
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis_prov, $loc_key, $status_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->loc_key = $loc_key;
$this->status_key = $status_key;
$this->em = $em;
}
public function addActiveRider($id, $lat, $lng)
@ -46,10 +50,15 @@ class RiderCache
$lng = $data[1][0];
$lat = $data[1][1];
$locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
// get rider details so we can check for availability
$rider = $this->getRiderDetails($id);
if ($rider != null)
{
$locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
}
}
// error_log(print_r($all_riders, true));
@ -73,4 +82,17 @@ class RiderCache
{
$this->redis->hincrby($this->status_key, $id, -1);
}
protected function getRiderDetails($id)
{
$rider = $this->em->getRepository(Rider::class)->find($id);
if ($rider == null)
return null;
// return only if available
if ($rider->isAvailable())
return $rider;
return null;
}
}

View file

@ -448,217 +448,50 @@
<div id="m_header_topbar" class="m-topbar m-stack m-stack--ver m-stack--general">
<div class="m-stack__item m-topbar__nav-wrapper">
<ul class="m-topbar__nav m-nav m-nav--inline">
<!--
<li class="m-nav__item m-topbar__notifications m-topbar__notifications--img m-dropdown m-dropdown--large m-dropdown--header-bg-fill m-dropdown--arrow m-dropdown--align-center m-dropdown--mobile-full-width" data-dropdown-toggle="click" data-dropdown-persistent="true">
<a href="#" class="m-nav__link m-dropdown__toggle" id="m_topbar_notification_icon">
<span class="m-nav__link-badge m-badge m-badge--dot m-badge--dot-small m-badge--danger"></span>
<span class="m-nav__link-icon">
<i class="flaticon-music-2"></i>
</span>
</a>
<div class="m-dropdown__wrapper">
<span class="m-dropdown__arrow m-dropdown__arrow--center"></span>
<div class="m-dropdown__inner">
<div class="m-dropdown__header m--align-center" style="background: url(assets/app/media/img/misc/notification_bg.jpg); background-size: cover;">
<span class="m-dropdown__header-title">
9 New
</span>
<span class="m-dropdown__header-subtitle">
User Notifications
</span>
</div>
<div class="m-dropdown__body">
<div class="m-dropdown__content">
<ul class="nav nav-tabs m-tabs m-tabs-line m-tabs-line--brand" role="tablist">
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link active" data-toggle="tab" href="#topbar_notifications_notifications" role="tab">
Alerts
</a>
</li>
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link" data-toggle="tab" href="#topbar_notifications_events" role="tab">
Events
</a>
</li>
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link" data-toggle="tab" href="#topbar_notifications_logs" role="tab">
Logs
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="topbar_notifications_notifications" role="tabpanel">
<div class="m-scrollable" data-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>
<span class="m-list-timeline__text">
12 new users registered
</span>
<span class="m-list-timeline__time">
Just now
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
System shutdown
<span class="m-badge m-badge--success m-badge--wide">
pending
</span>
</span>
<span class="m-list-timeline__time">
14 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
New invoice received
</span>
<span class="m-list-timeline__time">
20 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
DB overloaded 80%
<span class="m-badge m-badge--info m-badge--wide">
settled
</span>
</span>
<span class="m-list-timeline__time">
1 hr
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
System error -
<a href="#" class="m-link">
Check
</a>
</span>
<span class="m-list-timeline__time">
2 hrs
</span>
</div>
<div class="m-list-timeline__item m-list-timeline__item--read">
<span class="m-list-timeline__badge"></span>
<span href="" class="m-list-timeline__text">
New order received
<span class="m-badge m-badge--danger m-badge--wide">
urgent
</span>
</span>
<span class="m-list-timeline__time">
7 hrs
</span>
</div>
<div class="m-list-timeline__item m-list-timeline__item--read">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
Production server down
</span>
<span class="m-list-timeline__time">
3 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
Production server up
</span>
<span class="m-list-timeline__time">
5 hrs
</span>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="topbar_notifications_events" role="tabpanel">
<div class="m-scrollable" m-scrollabledata-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-success"></span>
<a href="" class="m-list-timeline__text">
New order received
</a>
<span class="m-list-timeline__time">
Just now
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-danger"></span>
<a href="" class="m-list-timeline__text">
New invoice received
</a>
<span class="m-list-timeline__time">
20 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-success"></span>
<a href="" class="m-list-timeline__text">
Production server up
</a>
<span class="m-list-timeline__time">
5 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
New order received
</a>
<span class="m-list-timeline__time">
7 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
System shutdown
</a>
<span class="m-list-timeline__time">
11 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
Production server down
</a>
<span class="m-list-timeline__time">
3 hrs
</span>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="topbar_notifications_logs" role="tabpanel">
<div class="m-stack m-stack--ver m-stack--general" style="min-height: 180px;">
<div class="m-stack__item m-stack__item--center m-stack__item--middle">
<span class="">
All caught up!
<br>
No new logs.
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
-->
<li class="m-nav__item m-topbar__notifications m-topbar__notifications--img m-dropdown m-dropdown--large m-dropdown--header-bg-fill m-dropdown--arrow m-dropdown--align-right m-dropdown--mobile-full-width" data-dropdown-toggle="click" data-dropdown-persistent="true">
<a href="#" class="m-nav__link m-dropdown__toggle" id="m_topbar_notification_icon">
<span class="m-nav__link-badge m-badge m-badge--dot m-badge--dot-small m-badge--danger"></span>
<span class="m-nav__link-icon">
<i class="flaticon-music-2"></i>
</span>
</a>
<div class="m-dropdown__wrapper">
<span class="m-dropdown__arrow m-dropdown__arrow--right m-dropdown__arrow--adjust"></span>
<div class="m-dropdown__inner">
<div class="m-dropdown__header m--align-center" style="background: url(/assets/app/media/img/misc/notification_bg.jpg); background-size: cover;">
<span class="m-dropdown__header-title">
<span id="notif-count">-</span> New
</span>
<span class="m-dropdown__header-subtitle">
User Notifications
</span>
</div>
<div class="m-dropdown__body">
<div class="m-dropdown__content">
<div class="m-scrollable" data-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div id="notif-body" class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>
<span class="m-list-timeline__text">
<a href="#">This is a notification</a>
</span>
<span class="m-list-timeline__time">
Just now
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
<li class="m-nav__item m-topbar__user-profile m-topbar__user-profile--img m-dropdown m-dropdown--medium m-dropdown--arrow m-dropdown--header-bg-fill m-dropdown--align-right m-dropdown--mobile-full-width m-dropdown--skin-light" data-dropdown-toggle="click">
<a href="#" class="m-nav__link m-dropdown__toggle">
<span class="m-topbar__userpic">
@ -718,6 +551,8 @@
</div>
</div>
</li>
</ul>
</div>
</div>
@ -770,6 +605,26 @@
<!--end::Page Snippets -->
<!--begin::Extra Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<script src="/assets/js/moment.min.js" type="text/javascript"></script>
<script src="/assets/js/notification.js" type="text/javascript"></script>
<script>
// notifications
var notif = new NotificationHandler({
'notif_ajax_url': '{{ url('notification_ajax_list') }}',
'notif_ajax_update_url': '{{ url('notification_ajax_update') }}',
'icons': {
'jo_new': 'flaticon-placeholder-3 kt-font-brand',
'jo_cancel': 'fa fa-times-circle kt-font-danger',
'rider_accept': 'fa fa-motorcycle kt-font-success',
'rider_reject': 'fa fa-exclamation-triangle kt-font-danger'
},
'default_icon': 'fa fa-asterisk kt-font-brand',
});
notif.clearAll();
notif.loadAll();
notif.listen('{{ app.user.id }}', '{{ mqtt_host }}', {{ mqtt_port }}, {{ ssl_enable }});
</script>
{% block scripts %}{% endblock %}
<!--end::Extra Scripts -->
</body>

15
utils/clear_jo_data.sql Normal file
View file

@ -0,0 +1,15 @@
delete from jo_event;
delete from invoice_item;
delete from invoice;
delete from ticket;
set foreign_key_checks = 0;
delete from job_order;
set foreign_key_checks = 1;
delete from mobile_session;
delete from customer_vehicle;
delete from customer;
delete from warranty;
update rider set active_jo_id = null;