435 lines
9.6 KiB
PHP
435 lines
9.6 KiB
PHP
<?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>
|