parent
e47e465e9d
commit
108bde4f1e
|
|
@ -1020,6 +1020,10 @@ $categoryLabels = [
|
|||
|
||||
<!-- Button -->
|
||||
<div class="d-flex iw-ui-control align-items-center ms-auto gap-2">
|
||||
<button id="btnRouteSchedule" class="btn btn-outline-secondary" onclick="previewPopup();">
|
||||
<i class="bi bi-eye"></i> Preview
|
||||
</button>
|
||||
|
||||
<button id="btnRouteSchedule" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-geo-alt"></i> Route
|
||||
</button>
|
||||
|
|
@ -2226,6 +2230,22 @@ while ($row = mysqli_fetch_assoc($rmem)) {
|
|||
|
||||
unassignDailyInstall(ids);
|
||||
});
|
||||
|
||||
function previewPopup() {
|
||||
const startDate = '<?= date('Y-m-d') ?>';
|
||||
const endDate = '<?= date('Y-m-d', strtotime('+6 days')) ?>';
|
||||
|
||||
const url =
|
||||
`/lib/install_preview_popup.php` +
|
||||
`?startDate=${startDate}` +
|
||||
`&endDate=${endDate}`;
|
||||
|
||||
window.open(
|
||||
url,
|
||||
"installPreviewPopup",
|
||||
"width=1400,height=900,resizable=yes,scrollbars=yes"
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<script src="/js/install_wait_modal.js?v=<?=time()?>"></script>
|
||||
<script src="/js/customer_image.js?v=<?=time()?>"></script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,435 @@
|
|||
<?php
|
||||
include getenv("DOCUMENT_ROOT")."/include/session_include.php";
|
||||
require_once getenv("DOCUMENT_ROOT")."/assets/dbCon.php";
|
||||
|
||||
$startDate = $_GET['startDate'];
|
||||
$endDate = $_GET['endDate'];
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
di.di_uid,
|
||||
di.di_install_date,
|
||||
di.di_driver_uid,
|
||||
di.di_address,
|
||||
di.di_city,
|
||||
di.di_lat,
|
||||
di.di_lng,
|
||||
d.dr_initial
|
||||
FROM tbl_daily_install di
|
||||
LEFT JOIN tbl_driver d
|
||||
ON d.dr_uid = di.di_driver_uid
|
||||
WHERE di.di_install_date BETWEEN '{$startDate}' AND '{$endDate}'
|
||||
ORDER BY di.di_install_date, di_driver_uid, di.di_lat, di.di_lng ASC
|
||||
";
|
||||
|
||||
$result = mysqli_query($connect, $sql);
|
||||
|
||||
$installData = [];
|
||||
$sq = qry($sql);
|
||||
|
||||
while($row = fetch_array($sq)) {
|
||||
$installData[] = $row;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Install Preview</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
|
||||
html,
|
||||
body{
|
||||
width:100%;
|
||||
height:100%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
background:#f5f6f8;
|
||||
font-family:Arial, sans-serif;
|
||||
}
|
||||
|
||||
#installMap{
|
||||
width:100%;
|
||||
height:100vh;
|
||||
}
|
||||
|
||||
.install-map-legend{
|
||||
position:absolute;
|
||||
top:15px;
|
||||
left:15px;
|
||||
z-index:999;
|
||||
background:#fff;
|
||||
border-radius:12px;
|
||||
padding:14px;
|
||||
min-width:220px;
|
||||
box-shadow:0 2px 12px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.legend-title{
|
||||
font-size:14px;
|
||||
font-weight:700;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.legend-item{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
margin-bottom:8px;
|
||||
font-size:13px;
|
||||
}
|
||||
|
||||
.legend-color{
|
||||
width:16px;
|
||||
height:16px;
|
||||
border-radius:50%;
|
||||
}
|
||||
|
||||
.date-pin{
|
||||
width:38px;
|
||||
height:38px;
|
||||
border-radius:50%;
|
||||
color:#fff;
|
||||
font-size:13px;
|
||||
font-weight:700;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
border:2px solid #fff;
|
||||
box-shadow:0 2px 8px rgba(0,0,0,.3);
|
||||
transition:all .15s ease;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.date-pin:hover{
|
||||
transform:scale(1.08);
|
||||
}
|
||||
|
||||
.map-header{
|
||||
position:absolute;
|
||||
top:15px;
|
||||
right:15px;
|
||||
z-index:999;
|
||||
background:#fff;
|
||||
border-radius:12px;
|
||||
padding:12px 16px;
|
||||
box-shadow:0 2px 12px rgba(0,0,0,.15);
|
||||
font-size:14px;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
.flag-marker{
|
||||
position:relative;
|
||||
min-width:90px;
|
||||
max-width:180px;
|
||||
text-align:center;
|
||||
transition:all .15s ease;
|
||||
cursor:pointer;
|
||||
opacity:.82;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.flag-marker:hover{
|
||||
transform:translateY(-2px);
|
||||
opacity:1;
|
||||
z-index:9999;
|
||||
}
|
||||
|
||||
.flag-date{
|
||||
font-size:13px;
|
||||
font-weight:700;
|
||||
padding-bottom:4px;
|
||||
margin-bottom:4px;
|
||||
border-bottom: 1px solid rgba(255,255,255,.25);
|
||||
}
|
||||
|
||||
.flag-cities{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:2px;
|
||||
}
|
||||
|
||||
.flag-city{
|
||||
white-space:nowrap;
|
||||
font-size:11px;
|
||||
line-height:1.15;
|
||||
}
|
||||
|
||||
.flag-tail{
|
||||
position:absolute;
|
||||
left:50%;
|
||||
bottom:-8px;
|
||||
transform:translateX(-50%);
|
||||
width:0;
|
||||
height:0;
|
||||
border-left:7px solid transparent;
|
||||
border-right:7px solid transparent;
|
||||
border-top:10px solid var(--flag-bg);
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<div class="map-header">
|
||||
Install Preview
|
||||
<span class="text-muted">
|
||||
<?=$startDate?> ~ <?=$endDate?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="installMapLegend" class="install-map-legend"></div>
|
||||
<div id="installMap"></div>
|
||||
|
||||
<script>
|
||||
let map;
|
||||
const installData = <?=json_encode($installData, JSON_UNESCAPED_UNICODE)?>;
|
||||
const driverPalette = [
|
||||
"#4275c1",
|
||||
"#91744fff",
|
||||
"#198754",
|
||||
"#dc3545",
|
||||
"#e97517ff",
|
||||
"#7443cfff",
|
||||
"#20c997",
|
||||
"#0dcaf0"
|
||||
];
|
||||
|
||||
// DRIVER COLOR
|
||||
function getDriverColor(driverUid) {
|
||||
return driverPalette[driverUid % driverPalette.length];
|
||||
}
|
||||
|
||||
// CUSTOM PIN
|
||||
function createPreviewMarker({ date, cities, color }) {
|
||||
|
||||
const bg = color;
|
||||
const wrap = document.createElement('div');
|
||||
|
||||
wrap.className = 'flag-marker';
|
||||
wrap.style.background = bg;
|
||||
wrap.style.border = `2px solid ${bg}`;
|
||||
wrap.style.color = "#ffffff";
|
||||
wrap.style.borderRadius = "10px";
|
||||
wrap.style.padding = "6px 10px";
|
||||
wrap.style.fontWeight = "700";
|
||||
wrap.style.fontSize = "12px";
|
||||
wrap.style.boxShadow = "0 2px 8px rgba(0,0,0,.25)";
|
||||
wrap.style.setProperty('--flag-bg', bg);
|
||||
wrap.innerHTML = `
|
||||
<div class="flag-date">
|
||||
${date}
|
||||
</div>
|
||||
<div class="flag-cities">
|
||||
${cities.map(city => `
|
||||
<div class="flag-city">${city}</div>
|
||||
`).join('')}
|
||||
|
||||
</div>
|
||||
<div class="flag-tail"></div>
|
||||
`;
|
||||
return wrap;
|
||||
}
|
||||
|
||||
// LEGEND
|
||||
function renderLegend(){
|
||||
|
||||
const el = document.getElementById("installMapLegend");
|
||||
if(!el) return;
|
||||
|
||||
let html = `<div class="legend-title">Driver Legend</div>`;
|
||||
const seen = new Set();
|
||||
installData.forEach(row => {
|
||||
|
||||
const uid = parseInt(row.di_driver_uid, 10);
|
||||
if(!uid || seen.has(uid)) return;
|
||||
|
||||
seen.add(uid);
|
||||
|
||||
const color = getDriverColor(uid);
|
||||
html += `
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background:${color}"></span>
|
||||
<span>${row.dr_initial || 'Unknown'}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
function getCityLabel(cityCounts){
|
||||
const sorted = Object.entries(cityCounts).sort((a, b) => b[1] - a[1]);
|
||||
if(sorted.length === 0) return '';
|
||||
return sorted.map(x => x[0]).join(', ');
|
||||
}
|
||||
|
||||
// INIT MAP
|
||||
async function initMap(){
|
||||
|
||||
const { Map, InfoWindow } = await google.maps.importLibrary("maps");
|
||||
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
|
||||
map = new Map(
|
||||
document.getElementById("installMap"),
|
||||
{
|
||||
center: {
|
||||
lat: 43.7,
|
||||
lng: -79.4
|
||||
},
|
||||
zoom: 11,
|
||||
mapId: "4504f8b37365c3d0",
|
||||
disableDefaultUI: true
|
||||
}
|
||||
);
|
||||
|
||||
renderLegend();
|
||||
|
||||
const bounds = new google.maps.LatLngBounds();
|
||||
// POLYGON
|
||||
const grouped = {};
|
||||
installData.forEach(row => {
|
||||
|
||||
const key = `${row.di_driver_uid}_${row.di_install_date}`;
|
||||
|
||||
if(!grouped[key]){
|
||||
grouped[key] = {
|
||||
driverUid: row.di_driver_uid,
|
||||
installDate: row.di_install_date,
|
||||
rows: [],
|
||||
cityCounts: {}
|
||||
};
|
||||
}
|
||||
|
||||
grouped[key].rows.push(row);
|
||||
|
||||
const city = (row.di_city || '').trim();
|
||||
|
||||
if(city){
|
||||
grouped[key].cityCounts[city] =
|
||||
(grouped[key].cityCounts[city] || 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
const driverInitialMap = {};
|
||||
|
||||
installData.forEach(row => {
|
||||
if(!driverInitialMap[row.di_driver_uid]){
|
||||
driverInitialMap[row.di_driver_uid] = row.dr_initial || 'Unknown';
|
||||
}
|
||||
});
|
||||
|
||||
Object.values(grouped).forEach(group => {
|
||||
|
||||
const points = [];
|
||||
group.rows.forEach(row => {
|
||||
if(!row.di_lat || !row.di_lng) return;
|
||||
points.push({
|
||||
lat: parseFloat(row.di_lat),
|
||||
lng: parseFloat(row.di_lng)
|
||||
});
|
||||
});
|
||||
if(points.length === 0) return;
|
||||
|
||||
// CENTER
|
||||
const avgLat = points.reduce((a,b)=>a+b.lat,0) / points.length;
|
||||
const avgLng = points.reduce((a,b)=>a+b.lng,0) / points.length;
|
||||
const center = {
|
||||
lat: avgLat,
|
||||
lng: avgLng
|
||||
};
|
||||
|
||||
// COLOR
|
||||
const color = getDriverColor(group.driverUid);
|
||||
|
||||
// POLYGON
|
||||
if(points.length >= 3){
|
||||
|
||||
const polygon =
|
||||
new google.maps.Polygon({
|
||||
paths: points,
|
||||
strokeColor: color,
|
||||
strokeOpacity: 0.9,
|
||||
strokeWeight: 2,
|
||||
fillColor: color,
|
||||
fillOpacity: 0.12
|
||||
});
|
||||
polygon.setMap(map);
|
||||
}
|
||||
|
||||
// DATE
|
||||
const [year, month, day] = group.installDate.split('-');
|
||||
const displayDate = `${parseInt(month)}/${parseInt(day)}`;
|
||||
const cityList = Object.keys(group.cityCounts);
|
||||
|
||||
// MARKER
|
||||
const offsetLat = (Math.random() - 0.5) * 0.03;
|
||||
const offsetLng = (Math.random() - 0.5) * 0.03;
|
||||
const marker = new google.maps.marker.AdvancedMarkerElement({
|
||||
map,
|
||||
position: {
|
||||
lat: center.lat + offsetLat,
|
||||
lng: center.lng + offsetLng
|
||||
},
|
||||
zIndex: 1,
|
||||
content: createPreviewMarker({
|
||||
date: displayDate,
|
||||
cities: cityList,
|
||||
color: color
|
||||
})
|
||||
});
|
||||
|
||||
// MOUSE OVER
|
||||
const markerEl = marker.content;
|
||||
markerEl.addEventListener('mouseenter', () => {
|
||||
marker.zIndex = 9999;
|
||||
});
|
||||
markerEl.addEventListener('mouseleave', () => {
|
||||
marker.zIndex = 1;
|
||||
});
|
||||
|
||||
// INFO
|
||||
const infoWindow =
|
||||
new google.maps.InfoWindow({
|
||||
content: `
|
||||
<div style="min-width:220px;">
|
||||
<div class="fw-bold mb-2">${group.city}</div>
|
||||
<div><b>Date:</b>${group.installDate}</div>
|
||||
<div><b>Jobs:</b>${group.rows.length}</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
/*
|
||||
marker.addListener('gmp-click', () => {
|
||||
infoWindow.open({
|
||||
anchor: marker,
|
||||
map
|
||||
});
|
||||
});
|
||||
*/
|
||||
bounds.extend(center);
|
||||
});
|
||||
|
||||
// AUTO FIT
|
||||
if(installData.length > 0){
|
||||
map.fitBounds(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
window.initMap = initMap;
|
||||
|
||||
</script>
|
||||
|
||||
<!-- GOOGLE MAP -->
|
||||
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDg9u03mGrBhyOisp7VGc27CTPI9QXp8sY&libraries=marker&callback=initMap"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue