map and customer geo location added

added map and customer geo location, we advice you to change/edit the customer coordinates according to their location, so that you can see where the customer is located on the Map
This commit is contained in:
Focuslinkstech 2024-03-19 00:59:34 +01:00
parent 605fbb73a6
commit f27964dde6
8 changed files with 214 additions and 77 deletions

View file

@ -130,7 +130,7 @@ switch ($action) {
$ui->assign('using', 'cash');
$ui->assign('plan', $plan);
$ui->display('recharge-confirm.tpl');
}else{
} else {
r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
}
break;
@ -191,7 +191,7 @@ switch ($action) {
}
}
}
r2(U . 'customers/view/' . $id_customer, 's', 'Sync success to '.implode(", ",$routers));
r2(U . 'customers/view/' . $id_customer, 's', 'Sync success to ' . implode(", ", $routers));
}
r2(U . 'customers/view/' . $id_customer, 'e', 'Cannot find active plan');
break;
@ -212,7 +212,7 @@ switch ($action) {
->find_many();
$v = $routes['3'];
if(empty($v)){
if (empty($v)) {
$v = 'activation';
}
if ($v == 'order') {
@ -328,6 +328,7 @@ switch ($action) {
$address = _post('address');
$phonenumber = _post('phonenumber');
$service_type = _post('service_type');
$coordinates = _post('coordinates');
//post Customers Attributes
$custom_field_names = (array) $_POST['custom_field_name'];
$custom_field_values = (array) $_POST['custom_field_value'];
@ -360,6 +361,7 @@ switch ($action) {
$d->created_by = $admin['id'];
$d->phonenumber = Lang::phoneFormat($phonenumber);
$d->service_type = $service_type;
$d->coordinates = $coordinates;
$d->save();
// Retrieve the customer ID of the newly created customer
@ -395,6 +397,7 @@ switch ($action) {
$address = _post('address');
$phonenumber = Lang::phoneFormat(_post('phonenumber'));
$service_type = _post('service_type');
$coordinates = _post('coordinates');
run_hook('edit_customer'); #HOOK
$msg = '';
if (Validator::Length($username, 35, 2) == false) {
@ -454,6 +457,7 @@ switch ($action) {
$d->address = $address;
$d->phonenumber = $phonenumber;
$d->service_type = $service_type;
$d->coordinates = $coordinates;
$d->save();

View file

@ -0,0 +1,45 @@
<?php
/**
* PHP Mikrotik Billing (https://github.com/hotspotbilling/phpnuxbill/)
* by https://t.me/ibnux
**/
_admin();
$ui->assign('_system_menu', 'map');
$action = $routes['1'];
$ui->assign('_admin', $admin);
if (empty($action)) {
$action = 'customer';
}
switch ($action) {
case 'customer':
$c = ORM::for_table('tbl_customers')->find_many();
$customerData = [];
foreach ($c as $customer) {
$customerData[] = [
'id' => $customer->id,
'name' => $customer->fullname,
'balance' => $customer->balance,
'address' => $customer->address,
'info' => Lang::T("Username") . ": " . $customer->username . " - " . Lang::T("Full Name") . ": " . $customer->fullname . " - " . Lang::T("Email") . ": " . $customer->email . " - " . Lang::T("Phone") . ": " . $customer->phonenumber . " - " . Lang::T("Service Type") . ": " . $customer->service_type,
'coordinates' => '[' . $customer->coordinates . ']',
];
}
$ui->assign('customers', $customerData);
$ui->assign('xheader', '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css">');
$ui->assign('_title', Lang::T('Customer Geo Location Information'));
$ui->assign('xfooter', '<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>');
$ui->display('map-customer.tpl');
break;
default:
r2(U . 'map/customer', 'e', 'action not defined');
break;
}

View file

@ -78,5 +78,10 @@
],
"2024.3.14" : [
"ALTER TABLE `tbl_transactions` ADD `note` VARCHAR(256) NOT NULL DEFAULT '' COMMENT 'for note' AFTER `type`;"
],
"2024.3.19" : [
"ALTER TABLE `tbl_customers` ADD `coordinates` VARCHAR(50) NOT NULL DEFAULT '6.465422, 3.406448' COMMENT 'Latitude and Longitude coordinates' AFTER `email`;"
]
}

View file

@ -11,10 +11,10 @@
<div class="col-md-9">
<div class="input-group">
{if $_c['country_code_phone']!= ''}
<span class="input-group-addon" id="basic-addon1">+</span>
<span class="input-group-addon" id="basic-addon1">+</span>
{else}
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
{/if}
<input type="text" class="form-control" name="username" required
placeholder="{if $_c['country_code_phone']!= ''}{$_c['country_code_phone']}{/if} {Lang::T('Phone Number')}">
@ -38,10 +38,10 @@
<div class="col-md-9">
<div class="input-group">
{if $_c['country_code_phone']!= ''}
<span class="input-group-addon" id="basic-addon1">+</span>
<span class="input-group-addon" id="basic-addon1">+</span>
{else}
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
{/if}
<input type="text" class="form-control" name="phonenumber"
placeholder="{if $_c['country_code_phone']!= ''}{$_c['country_code_phone']}{/if} {Lang::T('Phone Number')}">
@ -51,8 +51,9 @@
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Password')}</label>
<div class="col-md-9">
<input type="password" class="form-control" autocomplete="off" required id="password" value="{rand(000000,999999)}"
name="password" onmouseleave="this.type = 'password'" onmouseenter="this.type = 'text'">
<input type="password" class="form-control" autocomplete="off" required id="password"
value="{rand(000000,999999)}" name="password" onmouseleave="this.type = 'password'"
onmouseenter="this.type = 'text'">
</div>
</div>
<div class="form-group">
@ -72,6 +73,17 @@
<textarea name="address" id="address" class="form-control"></textarea>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Coordinates')}</label>
<div class="col-md-9">
<input name="coordinates" id="coordinates" class="form-control" value=""
placeholder="6.465422, 3.406448">
<span class="help-block">
<small>{Lang::T('Latitude and Longitude coordinates for map must be separate with comma
","')}</small>
</span>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Service Type')}</label>
<div class="col-md-9">
@ -110,16 +122,16 @@
</center>
</form>
{literal}
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
var customFieldsContainer = document.getElementById('custom-fields-container');
var addCustomFieldButton = document.getElementById('add-custom-field');
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
var customFieldsContainer = document.getElementById('custom-fields-container');
var addCustomFieldButton = document.getElementById('add-custom-field');
addCustomFieldButton.addEventListener('click', function() {
var fieldIndex = customFieldsContainer.children.length;
var newField = document.createElement('div');
newField.className = 'form-group';
newField.innerHTML = `
addCustomFieldButton.addEventListener('click', function () {
var fieldIndex = customFieldsContainer.children.length;
var newField = document.createElement('div');
newField.className = 'form-group';
newField.innerHTML = `
<div class="col-md-4">
<input type="text" class="form-control" name="custom_field_name[]" placeholder="Name">
</div>
@ -130,17 +142,17 @@
<button type="button" class="remove-custom-field btn btn-danger btn-sm">-</button>
</div>
`;
customFieldsContainer.appendChild(newField);
});
customFieldsContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('remove-custom-field')) {
var fieldContainer = event.target.parentNode.parentNode;
fieldContainer.parentNode.removeChild(fieldContainer);
}
});
customFieldsContainer.appendChild(newField);
});
</script>
customFieldsContainer.addEventListener('click', function (event) {
if (event.target.classList.contains('remove-custom-field')) {
var fieldContainer = event.target.parentNode.parentNode;
fieldContainer.parentNode.removeChild(fieldContainer);
}
});
});
</script>
{/literal}

View file

@ -12,10 +12,10 @@
<div class="col-md-9">
<div class="input-group">
{if $_c['country_code_phone']!= ''}
<span class="input-group-addon" id="basic-addon1">+</span>
<span class="input-group-addon" id="basic-addon1">+</span>
{else}
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
{/if}
<input type="text" class="form-control" name="username" value="{$d['username']}"
required
@ -41,10 +41,10 @@
<div class="col-md-9">
<div class="input-group">
{if $_c['country_code_phone']!= ''}
<span class="input-group-addon" id="basic-addon1">+</span>
<span class="input-group-addon" id="basic-addon1">+</span>
{else}
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
<span class="input-group-addon" id="basic-addon1"><i
class="glyphicon glyphicon-phone-alt"></i></span>
{/if}
<input type="text" class="form-control" name="phonenumber" value="{$d['phonenumber']}"
placeholder="{if $_c['country_code_phone']!= ''}{$_c['country_code_phone']}{/if} {Lang::T('Phone Number')}">
@ -77,6 +77,16 @@
<textarea name="address" id="address" class="form-control">{$d['address']}</textarea>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Coordinates')}</label>
<div class="col-md-9">
<input name="coordinates" id="coordinates" class="form-control" value="{$d['coordinates']}">
<span class="help-block">
<small>{Lang::T('Latitude and Longitude coordinates for map must be separate with comma
","')}</small>
</span>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Service Type')}</label>
<div class="col-md-9">
@ -97,19 +107,20 @@
<div class="panel-body">
<!--Customers Attributes edit start -->
{if $customFields}
{foreach $customFields as $customField}
<div class="form-group">
<label class="col-md-4 control-label"
for="{$customField.field_name}">{$customField.field_name}</label>
<div class="col-md-6">
<input class="form-control" type="text" name="custom_fields[{$customField.field_name}]"
id="{$customField.field_name}" value="{$customField.field_value}">
</div>
<label class="col-md-2">
<input type="checkbox" name="delete_custom_fields[]" value="{$customField.field_name}"> Delete
</label>
</div>
{/foreach}
{foreach $customFields as $customField}
<div class="form-group">
<label class="col-md-4 control-label"
for="{$customField.field_name}">{$customField.field_name}</label>
<div class="col-md-6">
<input class="form-control" type="text" name="custom_fields[{$customField.field_name}]"
id="{$customField.field_name}" value="{$customField.field_value}">
</div>
<label class="col-md-2">
<input type="checkbox" name="delete_custom_fields[]" value="{$customField.field_name}">
Delete
</label>
</div>
{/foreach}
{/if}
<!--Customers Attributes edit end -->
<!-- Customers Attributes add start -->
@ -133,16 +144,16 @@
</form>
{literal}
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
var customFieldsContainer = document.getElementById('custom-fields-container');
var addCustomFieldButton = document.getElementById('add-custom-field');
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
var customFieldsContainer = document.getElementById('custom-fields-container');
var addCustomFieldButton = document.getElementById('add-custom-field');
addCustomFieldButton.addEventListener('click', function() {
var fieldIndex = customFieldsContainer.children.length;
var newField = document.createElement('div');
newField.className = 'form-group';
newField.innerHTML = `
addCustomFieldButton.addEventListener('click', function () {
var fieldIndex = customFieldsContainer.children.length;
var newField = document.createElement('div');
newField.className = 'form-group';
newField.innerHTML = `
<div class="col-md-4">
<input type="text" class="form-control" name="custom_field_name[]" placeholder="Name">
</div>
@ -153,17 +164,17 @@
<button type="button" class="remove-custom-field btn btn-danger btn-sm">-</button>
</div>
`;
customFieldsContainer.appendChild(newField);
});
customFieldsContainer.addEventListener('click', function(event) {
if (event.target.classList.contains('remove-custom-field')) {
var fieldContainer = event.target.parentNode.parentNode;
fieldContainer.parentNode.removeChild(fieldContainer);
}
});
customFieldsContainer.appendChild(newField);
});
</script>
customFieldsContainer.addEventListener('click', function (event) {
if (event.target.classList.contains('remove-custom-field')) {
var fieldContainer = event.target.parentNode.parentNode;
fieldContainer.parentNode.removeChild(fieldContainer);
}
});
});
</script>
{/literal}
{include file="sections/footer.tpl"}

View file

@ -37,6 +37,9 @@
onclick="this.select()">
</li>
{/if}
<li class="list-group-item">
<b>{Lang::T('Coordinates')}</b> <span class="pull-right">{Lang::T($d['coordinates'])}</span>
</li>
<!--Customers Attributes view start -->
{if $customFields}
{foreach $customFields as $customField}
@ -74,16 +77,13 @@
</ul>
<div class="row">
<div class="col-xs-4">
<a href="{$_url}customers/list" class="btn btn-primary btn-sm btn-block">{Lang::T('Back')}</a>
<a href="{$_url}customers/delete/{$d['id']}" id="{$d['id']}"
class="btn btn-danger btn-block btn-sm"
onclick="return confirm('{Lang::T('Delete')}?')"><span class="fa fa-trash"></span></a>
</div>
<div class="col-xs-4">
<a href="{$_url}customers/sync/{$d['id']}"
onclick="return confirm('This will sync Customer to Mikrotik?')"
class="btn btn-info btn-sm btn-block">{Lang::T('Sync')}</a>
</div>
<div class="col-xs-4">
<a href="{$_url}message/send/{$d['id']}" class="btn btn-success btn-sm btn-block">{Lang::T('Send
Message')}</a>
<div class="col-xs-8">
<a href="{$_url}customers/edit/{$d['id']}"
class="btn btn-warning btn-sm btn-block">{Lang::T('Edit')}</a>
</div>
</div>
</div>

46
ui/ui/map-customer.tpl Normal file
View file

@ -0,0 +1,46 @@
{include file="sections/header.tpl"}
<!-- Map container div -->
<div id="map" style="width: 800px; height: 600px; margin: 20px auto"></div>
{literal}
<script>
window.onload = function() {
var map = L.map('map').setView([51.505, -0.09], 13);
var group = L.featureGroup().addTo(map);
var customers = {/literal}{$customers|json_encode}{literal};
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 20
}).addTo(map);
customers.forEach(function(customer) {
var name = customer.id;
var name = customer.name;
var info = customer.info;
var coordinates = customer.coordinates;
var balance = customer.balance;
var address = customer.address;
// Create a popup for the marker
var popupContent = "<strong>Customer Name</strong>: " + name + "<br>" +
"<strong>Customer Info</strong>: " + info + "<br>" +
"<strong>Customer Balance</strong>: " + balance + "<br>" +
"<strong>Address</strong>: " + address + "<br>" +
"<strong>Coordinates</strong>: " + coordinates + "<br>" +
"<a href='{$_url}customers/view/"+ customer.id +"'>More Info</a><br>";
// Add marker to map
var marker = L.marker(JSON.parse(coordinates)).addTo(group);
marker.bindTooltip(name).bindPopup(popupContent);
});
map.fitBounds(group.getBounds());
}
</script>
{/literal}
{include file="sections/footer.tpl"}

View file

@ -234,6 +234,20 @@
</ul>
</li>
{$_MENU_AFTER_REPORTS}
<li class="{if $_system_menu eq 'map'}active{/if} treeview">
<a href="#">
<i class="ion ion-ios-location"></i> <span>{Lang::T('Map')}</span>
<span class="pull-right-container">
<i class="fa fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
<li {if $_routes[1] eq 'customer' }class="active" {/if}><a
href="{$_url}map/customer">{Lang::T('Customer Location')}</a></li>
{$_MENU_MAP}
</ul>
</li>
{$_MENU_AFTER_MAP}
<li class="{if $_system_menu eq 'message'}active{/if} treeview">
<a href="#">
<i class="ion ion-android-chat"></i> <span>{Lang::T('Send Message')}</span>