Add customer jo tracking view #315
This commit is contained in:
parent
8d45b8e698
commit
e914c6d2e1
7 changed files with 410 additions and 0 deletions
|
|
@ -21,6 +21,11 @@ security:
|
||||||
methods: [GET]
|
methods: [GET]
|
||||||
security: false
|
security: false
|
||||||
|
|
||||||
|
tracker:
|
||||||
|
pattern: ^\/track\/
|
||||||
|
methods: [GET]
|
||||||
|
security: false
|
||||||
|
|
||||||
api:
|
api:
|
||||||
pattern: ^\/api\/
|
pattern: ^\/api\/
|
||||||
security: false
|
security: false
|
||||||
|
|
|
||||||
|
|
@ -201,3 +201,8 @@ jo_ajax_popup:
|
||||||
controller: App\Controller\JobOrderController::popupInfo
|
controller: App\Controller\JobOrderController::popupInfo
|
||||||
methods: [GET]
|
methods: [GET]
|
||||||
|
|
||||||
|
jo_tracker:
|
||||||
|
path: /track/{id}
|
||||||
|
controller: App\Controller\JobOrderController::tracker
|
||||||
|
methods: [GET]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -331,4 +331,31 @@ span.has-danger,
|
||||||
.map-div-icon i.awesome {
|
.map-div-icon i.awesome {
|
||||||
margin: 12px auto;
|
margin: 12px auto;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 9999;
|
||||||
|
padding: 1.5em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info > .m-portlet {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info .m-portlet__body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info .rider-image {
|
||||||
|
width: 4.8rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-info .m-badge {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
182
public/assets/js/tracker_map.js
Normal file
182
public/assets/js/tracker_map.js
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
class DashboardMap {
|
||||||
|
constructor(options, rider_markers, cust_markers) {
|
||||||
|
this.options = options;
|
||||||
|
this.rider_markers = rider_markers;
|
||||||
|
this.cust_markers = cust_markers;
|
||||||
|
|
||||||
|
// layer groups
|
||||||
|
this.layer_groups = {
|
||||||
|
'rider_available': L.layerGroup(),
|
||||||
|
'rider_active_jo': L.layerGroup(),
|
||||||
|
'customer': L.layerGroup()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
// main map
|
||||||
|
this.map = L.map(this.options.div_id).setView(
|
||||||
|
[this.options.center_lat, this.options.center_lng],
|
||||||
|
this.options.zoom
|
||||||
|
);
|
||||||
|
|
||||||
|
// add tile layer
|
||||||
|
var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
|
||||||
|
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
|
||||||
|
maxZoom: 18,
|
||||||
|
id: 'mapbox/streets-v11',
|
||||||
|
accessToken: this.options.access_token
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
// layer groups
|
||||||
|
this.layer_groups.rider_available.addTo(this.map);
|
||||||
|
this.layer_groups.rider_active_jo.addTo(this.map);
|
||||||
|
this.layer_groups.customer.addTo(this.map);
|
||||||
|
|
||||||
|
// base layer
|
||||||
|
var baseMaps = {
|
||||||
|
'Streets': streets
|
||||||
|
};
|
||||||
|
|
||||||
|
// overlay layer
|
||||||
|
return this.map;
|
||||||
|
}
|
||||||
|
|
||||||
|
putMarker(id, lat, lng, markers, icon, layer_group, popup_url) {
|
||||||
|
var my = this;
|
||||||
|
// existing marker
|
||||||
|
if (markers.hasOwnProperty(id)) {
|
||||||
|
markers[id].setLatLng(L.latLng(lat, lng));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new marker
|
||||||
|
markers[id] = L.marker(
|
||||||
|
[lat, lng],
|
||||||
|
{ icon: icon }
|
||||||
|
).bindPopup('Loading...')
|
||||||
|
.addTo(layer_group);
|
||||||
|
|
||||||
|
// bind ajax for popup
|
||||||
|
markers[id].on('click', function(e) {
|
||||||
|
var popup = e.target.getPopup();
|
||||||
|
var url = popup_url.replace('[id]', id);
|
||||||
|
console.log(url);
|
||||||
|
$.get(url).done(function(data) {
|
||||||
|
popup.setContent(data);
|
||||||
|
popup.update();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
putCustomerMarker(id, lat, lng) {
|
||||||
|
this.putMarker(
|
||||||
|
id,
|
||||||
|
lat,
|
||||||
|
lng,
|
||||||
|
this.cust_markers,
|
||||||
|
this.options.icons.customer,
|
||||||
|
this.layer_groups.customer,
|
||||||
|
this.options.cust_popup_url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCustomerMarker(id) {
|
||||||
|
console.log('removing customer marker for ' + id);
|
||||||
|
var layer_group = this.layer_groups.customer;
|
||||||
|
var markers = this.cust_markers;
|
||||||
|
|
||||||
|
// no customer marker with that id
|
||||||
|
if (!markers.hasOwnProperty(id)) {
|
||||||
|
console.log('no such marker to remove');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_group.removeLayer(markers[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
putRiderAvailableMarker(id, lat, lng) {
|
||||||
|
this.putMarker(
|
||||||
|
id,
|
||||||
|
lat,
|
||||||
|
lng,
|
||||||
|
this.rider_markers,
|
||||||
|
this.options.icons.rider_available,
|
||||||
|
this.layer_groups.rider_available,
|
||||||
|
this.options.rider_popup_url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
putRiderActiveJOMarker(id, lat, lng) {
|
||||||
|
this.putMarker(
|
||||||
|
id,
|
||||||
|
lat,
|
||||||
|
lng,
|
||||||
|
this.rider_markers,
|
||||||
|
this.options.icons.rider_active_jo,
|
||||||
|
this.layer_groups.rider_active_jo,
|
||||||
|
this.options.rider_popup_url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLocations(location_url) {
|
||||||
|
console.log(this.rider_markers);
|
||||||
|
var my = this;
|
||||||
|
$.ajax({
|
||||||
|
url: location_url,
|
||||||
|
}).done(function(response) {
|
||||||
|
// clear all markers
|
||||||
|
my.layer_groups.rider_available.clearLayers();
|
||||||
|
my.layer_groups.rider_active_jo.clearLayers();
|
||||||
|
my.layer_groups.customer.clearLayers();
|
||||||
|
|
||||||
|
// get riders and job orders
|
||||||
|
var riders = response.riders;
|
||||||
|
var jos = response.jos;
|
||||||
|
|
||||||
|
// job orders
|
||||||
|
$.each(jos, function(id, data) {
|
||||||
|
var lat = data.latitude;
|
||||||
|
var lng = data.longitude;
|
||||||
|
|
||||||
|
my.putCustomerMarker(id, lat, lng);
|
||||||
|
});
|
||||||
|
|
||||||
|
// riders
|
||||||
|
$.each(riders, function(id, data) {
|
||||||
|
var lat = data.latitude;
|
||||||
|
var lng = data.longitude;
|
||||||
|
|
||||||
|
if (data.has_jo)
|
||||||
|
my.putRiderActiveJOMarker(id, lat, lng);
|
||||||
|
else
|
||||||
|
my.putRiderAvailableMarker(id, lat, lng);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
$.each(riders, function(rider_id, rider_data) {
|
||||||
|
// rider location
|
||||||
|
var point = rider_data['loc'];
|
||||||
|
var lat = point[0];
|
||||||
|
var lng = point[1];
|
||||||
|
|
||||||
|
// customer location
|
||||||
|
var cloc = rider_data['cust_loc'];
|
||||||
|
var clat = cloc[0];
|
||||||
|
var clng = cloc[1];
|
||||||
|
|
||||||
|
// create rider markers
|
||||||
|
if (rider_data['has_jo']) {
|
||||||
|
var jo_data = rider_data['jo'];
|
||||||
|
|
||||||
|
// my.putCustomerMarker(jo_data['id'], clat, clng);
|
||||||
|
my.putRiderActiveJOMarker(rider_id, lat, lng);
|
||||||
|
} else {
|
||||||
|
my.putRiderAvailableMarker(rider_id, lat, lng);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
// console.log(rider_markers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ namespace App\Controller;
|
||||||
|
|
||||||
use App\Ramcar\JOStatus;
|
use App\Ramcar\JOStatus;
|
||||||
use App\Ramcar\InvoiceCriteria;
|
use App\Ramcar\InvoiceCriteria;
|
||||||
|
use App\Ramcar\CMBServiceType;
|
||||||
|
|
||||||
use App\Entity\CustomerVehicle;
|
use App\Entity\CustomerVehicle;
|
||||||
use App\Entity\Promo;
|
use App\Entity\Promo;
|
||||||
|
|
@ -23,6 +24,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||||
|
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use App\Service\RiderTracker;
|
||||||
|
|
||||||
use Catalyst\MenuBundle\Annotation\Menu;
|
use Catalyst\MenuBundle\Annotation\Menu;
|
||||||
|
|
||||||
class JobOrderController extends Controller
|
class JobOrderController extends Controller
|
||||||
|
|
@ -922,4 +926,26 @@ class JobOrderController extends Controller
|
||||||
|
|
||||||
return $this->render('job-order/popup.html.twig', [ 'jo' => $jo ]);
|
return $this->render('job-order/popup.html.twig', [ 'jo' => $jo ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ParamConverter("jo", class="App\Entity\JobOrder")
|
||||||
|
*/
|
||||||
|
public function tracker(
|
||||||
|
EntityManagerInterface $em,
|
||||||
|
RiderTracker $rider_tracker,
|
||||||
|
GISManagerInterface $gis_manager,
|
||||||
|
JobOrder $jo
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if ($jo === null)
|
||||||
|
return new Response('No job order data');
|
||||||
|
|
||||||
|
// get map
|
||||||
|
$params['jo'] = $jo;
|
||||||
|
$params['rider'] = $jo->getRider();
|
||||||
|
$params['service_type'] = CMBServiceType::getName($jo->getServiceType());
|
||||||
|
$params['map_js_file'] = $gis_manager->getJSInitFile();
|
||||||
|
|
||||||
|
return $this->render('job-order/tracker.html.twig', $params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
templates/base_minimal.html.twig
Normal file
70
templates/base_minimal.html.twig
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
{% import 'menu.html.twig' as menu %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<!-- begin::Head -->
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{% block title %}{% trans %}block_title{% endtrans %}{% endblock %}</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
|
||||||
|
|
||||||
|
<!--begin::Web font -->
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.16/webfont.js"></script>
|
||||||
|
<script>
|
||||||
|
WebFont.load({
|
||||||
|
google: {"families":["Poppins:300,400,500,600,700","Roboto:300,400,500,600,700"]},
|
||||||
|
active: function() {
|
||||||
|
sessionStorage.fonts = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<!--end::Web font -->
|
||||||
|
|
||||||
|
<!--begin::Base Styles -->
|
||||||
|
<!--begin::Page Vendors -->
|
||||||
|
<link href="/assets/vendors/custom/fullcalendar/fullcalendar.bundle.css" rel="stylesheet" type="text/css" />
|
||||||
|
<!--end::Page Vendors -->
|
||||||
|
<link href="/assets/vendors/base/vendors.bundle.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="/assets/demo/default/base/style.bundle.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="/assets/css/style.css" rel="stylesheet" type="text/css" />
|
||||||
|
<!--end::Base Styles -->
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/favicon/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="{% trans %}icon_base_32x32{% endtrans %}">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="{% trans %}icon_base_16x16{% endtrans %}">
|
||||||
|
<link rel="manifest" href="/assets/images/favicon/manifest.json">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
|
<!--begin::Extra Styles -->
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
|
||||||
|
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
|
||||||
|
crossorigin=""/>
|
||||||
|
{% endblock %}
|
||||||
|
<!--end::Extra Styles -->
|
||||||
|
</head>
|
||||||
|
<!-- end::Head -->
|
||||||
|
<!-- end::Body -->
|
||||||
|
<body class="m-page--fluid m--skin- m-content--skin-light2 m-aside-left--enabled m-aside-left--skin-dark m-aside-left--offcanvas m-aside--offcanvas-default">
|
||||||
|
<!-- begin:: Page -->
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
<!-- end:: Page -->
|
||||||
|
|
||||||
|
<!--begin::Base Scripts -->
|
||||||
|
<script src="/assets/vendors/base/vendors.bundle.js" type="text/javascript"></script>
|
||||||
|
<script src="/assets/demo/default/base/scripts.bundle.js" type="text/javascript"></script>
|
||||||
|
<!--end::Base Scripts -->
|
||||||
|
<!--begin::Page Vendors -->
|
||||||
|
<script src="/assets/vendors/custom/fullcalendar/fullcalendar.bundle.js" type="text/javascript"></script>
|
||||||
|
<!--end::Page Vendors -->
|
||||||
|
<!--begin::Page Snippets -->
|
||||||
|
<script src="/assets/app/js/dashboard.js" type="text/javascript"></script>
|
||||||
|
<script src="/assets/js/common.js" type="text/javascript"></script>
|
||||||
|
<!--end::Page Snippets -->
|
||||||
|
|
||||||
|
<!--begin::Extra Scripts -->
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
<!--end::Extra Scripts -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
95
templates/job-order/tracker.html.twig
Normal file
95
templates/job-order/tracker.html.twig
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
{% extends 'base_minimal.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div id="tracker_map" style="height:100%;"></div>
|
||||||
|
<div class="map-info">
|
||||||
|
<div class="m-portlet m-portlet--mobile">
|
||||||
|
<div class="m-portlet__body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex flex-row justify-content-start">
|
||||||
|
<img class="mr-2 rider-image" src="{{ asset(rider.getImageFile ? "uploads/" . rider.getImageFile : "assets/images/user.gif") }}" alt="">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div><strong>Order #{{ jo.getID }}</strong></div>
|
||||||
|
<div>{{ rider.getFullName }}</div>
|
||||||
|
<div class="m-badge m-badge--brand m-badge--wide">{{ service_type }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
|
||||||
|
<script src="{{ asset('assets/js/tracker_map.js') }}"></script>
|
||||||
|
<script src="{{ asset('assets/js/map_mqtt.js') }}"></script>
|
||||||
|
{{ include('map/' ~ map_js_file) }}
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function initMap(r_markers, c_markers, icons) {
|
||||||
|
var default_lat = {% trans %}default_lat{% endtrans %};
|
||||||
|
var default_lng = {% trans %}default_long{% endtrans %};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
'access_token': 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw',
|
||||||
|
'div_id': 'tracker_map',
|
||||||
|
'center_lat': default_lat,
|
||||||
|
'center_lng': default_lng,
|
||||||
|
'map_type': 'road',
|
||||||
|
'zoom': 13,
|
||||||
|
'rider_popup_url': '/riders/[id]/popup',
|
||||||
|
'cust_popup_url': '/job-order/[id]/popup',
|
||||||
|
'icons': icons
|
||||||
|
};
|
||||||
|
|
||||||
|
var dashmap = new DashboardMap(options, r_markers, c_markers);
|
||||||
|
dashmap.initialize();
|
||||||
|
dashmap.loadLocations('{{ path('rider_locations') }}');
|
||||||
|
|
||||||
|
return dashmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initEventHandler(dashmap) {
|
||||||
|
var options = {
|
||||||
|
'channels': {
|
||||||
|
'rider_location': 'rider/+/location',
|
||||||
|
'jo_location': 'jo/+/location',
|
||||||
|
'jo_status': 'jo/+/status'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var event_handler = new MapEventHandler(options, dashmap);
|
||||||
|
event_handler.connect('tracker', '{{ mqtt_host }}', {{ mqtt_port }});
|
||||||
|
}
|
||||||
|
|
||||||
|
// create icons
|
||||||
|
var icons = {
|
||||||
|
'rider_active_jo': L.divIcon({
|
||||||
|
className: 'map-div-icon',
|
||||||
|
html: "<div style='background-color:#FF0000;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
|
||||||
|
iconSize: [39, 42],
|
||||||
|
iconAnchor: [15, 42]
|
||||||
|
}),
|
||||||
|
'rider_available': L.divIcon({
|
||||||
|
className: 'map-div-icon',
|
||||||
|
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
|
||||||
|
iconSize: [39, 42],
|
||||||
|
iconAnchor: [15, 42]
|
||||||
|
}),
|
||||||
|
'customer': L.divIcon({
|
||||||
|
className: 'map-div-icon',
|
||||||
|
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-user awesome'>",
|
||||||
|
iconSize: [39, 42],
|
||||||
|
iconAnchor: [15, 42]
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
var r_markers = {};
|
||||||
|
var c_markers = {};
|
||||||
|
|
||||||
|
var dashmap = initMap(r_markers, c_markers, icons);
|
||||||
|
initEventHandler(dashmap, icons);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in a new issue