goiintra/public_html/doc/map.php

3749 lines
144 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
//require_once ("/assets/dbCon.php");
require_once getenv("DOCUMENT_ROOT")."/assets/dbCon.php";
//Initialize date
$dateArr = array();
for($i=0;$i < 7;$i++) {
$dateValue = strtotime(date("Ymd")."+".$i." day");
//$dateValue = strtotime(date("20240425")."+".$i." day");
$dateArr[] = array(
"val" => date("Ymd", $dateValue),
"text" => date("Y-m-d", $dateValue)
);
}
//Load driver list
if ($_SESSION['ss_LEVEL'] == 9) {
$qrySTR = " AND (m_uid = '".$_SESSION['ss_UID']."') ";
} else {
$qrySTR = "";
}
$drivers = array();
$sqDriver = qry("SELECT
*
FROM
tbl_member
WHERE
m_level = 9 AND m_status = 'A' ".$qrySTR."
ORDER BY
m_initial ASC ");
while($rstDriver = fetch_array($sqDriver)){
$drivers[] = array(
"id" => $rstDriver['m_uid'],
"name" => $rstDriver['m_initial'],
"regionuid" => $rstDriver['m_regionuid'],
"region" => $rstDriver['m_region'],
"currentdriverid" => $rstDriver['m_currentdriver'],
"currentdriver" => $rstDriver['m_currentdriverinitial']
);
}
//Load quantity setting from cookie
$cookieValueArr = explode("_",$_COOKIE['qtySetting']);
$searchQty = array();
if(count($cookieValueArr) > 2){
for($k=0;$k < count($cookieValueArr);$k++) {
$cookieValue = explode(":",$cookieValueArr[$k]);
$searchQty[] = array(
"min" => $cookieValue[0],
"max" => $cookieValue[1]
);
}
}else{
$min = 0;
$max = 100;
for($k=0;$k < 5;$k++) {
$searchQty[] = array(
"min" => $min,
"max" => $max
);
$min = $max + 1;
$max = $min + 49;
}
}
if (($_SESSION['ss_DRUID'] != "")) {
if ($_POST['orderdate'] != "") $orderdateSTR = $_POST['orderdate'];
else $orderdateSTR = date('Y-m-d');
$orderdateSTR = str_replace('-', "", $orderdateSTR);
$sqQuantity = qry("SELECT
SUM(d_quantity) as pickupquantity,
COUNT(*) as pickupcnt
FROM
tbl_daily
WHERE
d_status = 'F' AND d_orderdate = '".$orderdateSTR."'
AND d_druid = '".$_SESSION['ss_DRUID']."' ");
$rstQuantity = fetch_array($sqQuantity);
$curPickupQty = (int)$rstQuantity['pickupquantity'];
$curVisitCnt = (int)$rstQuantity['pickupcnt'];
$sqTarget = qry("SELECT
dut_target_qty,
dut_target_visit_cnt
FROM
tbl_daily_uco_target
WHERE
dut_date = '".$orderdateSTR."'
AND dut_druid = '".$_SESSION['ss_DRUID']."' ");
$rstTarget = fetch_array($sqTarget);
$curTargetQty = $rstTarget ? (int)$rstTarget['dut_target_qty'] : 0;
$curTargetVisitCnt = $rstTarget ? (int)$rstTarget['dut_target_visit_cnt'] : 0;
}
$strstr = "[".$_SESSION['ss_LEVEL']."][".$_SESSION['ss_UID']."][".$orderdateSTR."][".$rstQuantity['pickupquantity']."]";
?>
<link href="/assets/css/mapMAPCSS.css?v=<?=filemtime($_SERVER['DOCUMENT_ROOT'].'/assets/css/mapMAPCSS.css')?>" rel="stylesheet" type="text/css" >
<!-- New2 -->
<link href="/assets/css/leftside-modal.css?v=<?=filemtime($_SERVER['DOCUMENT_ROOT'].'/assets/css/leftside-modal.css')?>" rel="stylesheet">
<!-- New2 -->
<!--script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script-->
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=default"></script>
<script src="https://use.fontawesome.com/releases/v6.2.0/js/all.js"></script>
<script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
({key: "AIzaSyDg9u03mGrBhyOisp7VGc27CTPI9QXp8sY", v: "weekly"});</script>
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
<!-- New -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.0/jquery.validate.js"></script>
<div class="loading-overlay">Loading...</div>
<div id="popupMessage"></div>
<main class="pg-location">
<div class="width-20 floating-filter">
<form action="#" class="" >
<table id="sidebar-map" class="td-search">
<? //if (($_SESSION['ss_LEVEL'] == 9) && ($_SESSION['ss_UID'] != "")) { ?>
<? if (($_SESSION['ss_UID'] != "")) { ?>
<tr>
<td><button id="pickupQtyBtn" class="btn-blue-map btn-gauge" type="button"><span id="pickupquantity"><?=$curPickupQty?></span> / <span id="targetquantity"><?=$curTargetQty?></span>L<span class="gauge-sub"><span id="pickuppercent" class="gauge-pct"></span><span id="pickupvisit"><?=$curVisitCnt?></span> / <span id="targetvisit"><?=$curTargetVisitCnt?></span> visits</span></button></td>
</tr>
<? } ?>
<!-- <tr>
<td> <label for="orderBy">Order Date</label></td>
</tr> -->
<tr>
<td>
<input type="date" name="orderdate" id="orderdate" value="<?php echo date('Y-m-d');?>">
<!--select name="orderdate" id="orderdate" class="custom-select-map-orderby">
<?php
//foreach($dateArr as $dateDetail) {
// echo '<option value="'.$dateDetail['val'].'">'.$dateDetail['text'].'</option>';
//}
?>
</select-->
</td>
</tr>
<!--tr>
<td><label for="Type">Option</label></td>
</tr-->
<tr>
<td>
<label class="container-chk" style="clear:both">Ordered
<input type="checkbox" checked="checked" class="chk-ordered" disabled>
<span class="checkmark"></span>
</label>
<table>
<tr>
<td style="margin-left:4px;"><span style="width:15px; height:15px;display: block;" class="bg-color-ordered-normal"></span></td><td align=left>Nor </td>
<td><span style="width:15px; height:15px;display: block;" class="bg-color-ordered-requested"></span></td><td>Req</td>
<td><span style="width:15px; height:15px;display: block;" class="bg-color-ordered-scheduled"></span></td><td>Sch</td>
<td><span style="width:15px; height:15px;display: block;" class="bg-color-ordered-finished"></span></td><td>Fin</td>
</tr>
</table>
</td>
</tr>
<!-- <tr>
<td ><label for="orderBy">Driver</label></td>
</tr> -->
<tr>
<td>
<select name="name" id="driver" class="custom-select-map-orderby">
<?php
foreach($drivers as $driver) {
echo '<option value="'.$driver['currentdriverid'].'">'.$driver['name'].'</option>';
}
?>
</select>
</td>
</tr>
<!-- <tr>
<td><label for="Search">Search</label></td>
</tr> -->
<tr>
<td><input class="input-colour-bar" type="text" id="search-keyword" name="Search" placeholder="Search"></td>
</tr>
</table>
<table>
<!-- <tr>
<td colspan="4"><label for="Quantity">Quantity</label></td>
</tr> -->
<tr>
<td class="box-chk">
<label class="container-chk">
<input type="checkbox" class="qty">
<span class="checkmark"></span>
</label>
</td>
<td class="td-col-qty col-line">
<span class="level-line bg-color-yellow"></span>
</td>
<td class="td-col-qty col-left">
<input class="input-colour-bar qty-min" type="text" value="0" disabled>
</td>
<td class="td-col-qty col-second">~</td>
<td class="td-col-qty col-third">
<input class="input-colour-bar qty-max" type="text" value="<?php echo $searchQty[0]['max'];?>">
</td>
</tr>
<tr>
<td class="box-chk">
<label class="container-chk">
<input type="checkbox" class="qty">
<span class="checkmark"></span>
</label>
</td>
<td class="td-col-qty col-line">
<span class="level-line bg-color-orange"></span>
</td>
<td class="td-col-qty col-left">
<input class="input-colour-bar qty-min" type="text" value="<?php echo $searchQty[1]['min'];?>">
</td>
<td class="td-col-qty col-second">~</td>
<td class="td-col-qty col-third">
<input class="input-colour-bar qty-max" type="text" value="<?php echo $searchQty[1]['max'];?>">
</td>
</tr>
<tr>
<td class="box-chk">
<label class="container-chk">
<input type="checkbox" class="qty">
<span class="checkmark"></span>
</label>
</td>
<td class="td-col-qty col-line">
<span class="level-line bg-color-green"></span>
</td>
<td class="td-col-qty col-left">
<input class="input-colour-bar qty-min" type="text" value="<?php echo $searchQty[2]['min'];?>">
</td>
<td class="td-col-qty col-second">~</td>
<td class="td-col-qty col-third">
<input class="input-colour-bar qty-max" type="text" value="<?php echo $searchQty[2]['max'];?>">
</td>
</tr>
<tr>
<td class="box-chk">
<label class="container-chk">
<input type="checkbox" class="qty">
<span class="checkmark"></span>
</label>
</td>
<td class="td-col-qty col-line">
<span class="level-line bg-color-blue"></span>
</td>
<td class="td-col-qty col-left">
<input class="input-colour-bar qty-min" type="text" value="<?php echo $searchQty[3]['min'];?>">
</td>
<td class="td-col-qty col-second">~</td>
<td class="td-col-qty col-third">
<input class="input-colour-bar qty-max" type="text" value="<?php echo $searchQty[3]['max'];?>">
</td>
</tr>
<tr>
<td class="box-chk">
<label class="container-chk">
<input type="checkbox" class="qty">
<span class="checkmark"></span>
</label>
</td>
<td class="td-col-qty col-line" >
<span class="level-line bg-color-black"></span>
</td>
<td class="td-col-qty col-left">
<input class="input-colour-bar qty-min" type="text" value="<?php echo $searchQty[4]['min'];?>">
</td>
<td class="td-col-qty col-second">~</td>
<td class="td-col-qty col-third">
<input class="input-colour-bar qty-max" type="text" value="MAX" disabled>
</td>
</tr>
</table>
<div>
<button class="btn-blue-map btn-current" type="button">CURRENT</button>
<button class="btn-primary-map btn-routeplanner" type="button" data-toggle="modal" data-target="#routePlannerModal">ROUTE PLANNER</button>
<button class="btn-orange-map btn-route-print hidden" type="button">PRINT</button>
<button class="btn-red-map btn-init" type="button" onClick="forceRefresh()">INIT</button>
</div>
</form>
</div>
<div id="map"></div>
</main>
<!-- ROUTE PLANNER MODAL -->
<div class="modal fade" id="routePlannerModal" tabindex="-1" aria-labelledby="routePlannerModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="routePlannerModalLabel">Route Planner</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Make a list in the order you'd like to visit them. The place at the top will be counted as the Route you visit first.</p>
<div id="addressList" class="list-group">
</div>
<div id="addressInformationList" class="list-group">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary routePlannerApply" data-dismiss="modal">Apply</button>
</div>
</div>
</div>
</div>
<!-- ROUTE PLANNER MODAL END -->
<SCRIPT LANGUAGE='JAVASCRIPT'>
function popup(){
var orderdate = jQuery("#map-modal-input .d_orderdate").val();
var customeruid = jQuery("#map-modal-input .d_customeruid").val();
var customerno = jQuery("#map-modal-input .d_customerno").val();
var uid = jQuery("#map-modal-input .d_uid").val();
var str = '/lib/signature.php?'+'uid=' + uid + '&orderdate=' + orderdate + '&customeruid=' + customeruid + '&customerno=' + customerno;
window.open(str, '_blank', 'width=450,height=450,resizable=0,scrollbars=0');
}
</SCRIPT>
<!-- INPUT MODAL-->
<div class="modal fade top map" id="map-modal-input" tabindex="-1" role="dialog">
<div id="modal-input" class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">ORDER DETAIL</h4>
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<form name="pickupOrder" id="pickupOrder" method="POST" novalidate="novalidate">
<input type="hidden" class="d_uid">
<input type="hidden" class="d_customeruid">
<input type="hidden" class="d_customerno">
<input type="hidden" class="d_orderdate">
<input type="hidden" class="d_paymenttype">
<input type="hidden" class="d_driveruid">
<table class="tb-info-box">
<tbody>
<tr>
<td class="td-title-info">Restaurant Name</td>
<td class="td-text-info shortinput-store-name">Name</td>
</tr>
<tr>
<td class="td-title-info">Visited Date</td>
<td class="td-text-info">
<!--input type="datetime-local" class="d_visitdate"-->
<input type="date" class="d_visitdate">
</td>
</tr>
<tr>
<td class="td-title-info">Driver</td>
<td class="td-text-info">
<select class="custom-select d_druid">
<?php
foreach($drivers as $driver) {
echo '<option value="'.$driver['currentdriverid'].'">'.$driver['name'].'</option>';
}
?>
</select>
</td>
</tr>
<tr>
<td class="td-title-info">Rate</td>
<td class="td-text-info shortinput-rate">Rate</td>
</tr>
<tr>
<td class="td-title-info">Oil Quantity</td>
<td class="td-text-info">
<input type="number" class="d_quantity" placeholder="500" minlength="1" maxlength="10">
<div class="qty-error" style="color: red; font-size: 11px; display: none; margin-top: 4px;">Please input Oil Quantity</div>
</td>
</tr>
<tr>
<td class="td-title-info">Sludge (%)</td>
<td class="td-text-info">
<div class="number-input">
<button class="left" type="button">▼</button>
<input type="number" class="d_sludge" value="50" min="0" max="100" placeholder="50">
<button class="right" type="button">▲</button>
</div>
<div class="sludge-error" style="color: red; font-size: 11px; display: none; margin-top: 4px;">Please input the Sludge value.</div>
</td>
</tr>
</td>
</tr>
<tr class="CA_option">
<td class="td-title-info">Cash</td>
<td class="td-text-info">
<input type="text" class="d_payamount" placeholder="100">
</td>
</tr>
<tr class="CA_option">
<td class="td-title-info">Pay Status</td>
<td class="td-text-info" style="display:flex;width:100%">
<input type="radio" class="d_paystatuspaid" name="radio-group" value="P" style="margin-right:5px;width:auto;">
<span>Paid</span>
<input type="radio" class="d_paystatusunpaid" name="radio-group" value="N" style="margin-right:5px;margin-left:5px;width:auto;" checked>
<span>UnPaid</span>
</td>
</tr>
<tr class="CA_option">
<td class="td-title-info">Customer Name</td>
<td class="td-text-info">
<input type="text" class="d_payeename" placeholder="100">
</td>
</tr>
<tr class="CA_option">
<td class="td-title-info">Signature</td>
<td class="td-text-info">
<a href="#" onClick="popup();">CLICK</a>
</td>
</tr>
<tr>
<td class="td-title-info">Note</td>
<td class="td-text-info">
<textarea class="textarea d_note" rows="3" cols="20"></textarea>
</td>
</tr>
</tbody>
</table>
<br>
<div class="text-center grid-layout-col-2">
<button type="button" class="btn-sub btn-input-save">SAVE</button>
<button type="button" class="btn-gray" data-dismiss="modal">CLOSE</button>
</div>
</form>
</div>
</div><!-- modal-content ends -->
</div>
</div>
<!-- INPUT DETAIL MODAL Ends -->
<!-- ORDER MODAL-->
<div class="modal fade top map" id="map-modal-order" tabindex="-1" role="dialog">
<div id="modal-input" class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">ADD ORDER</h4>
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<form name="order-form" method="POST">
<input type="hidden" class="d_customeruid">
<input type="hidden" class="d_customerno">
<input type="hidden" class="d_orderdate">
<input type="hidden" class="c_index">
<table class="tb-info-box">
<tbody>
<!-- Order Type -->
<tr>
<td class="td-title-info">Order Type</td>
<td class="td-text-info">
<select class="custom-select d_ordertype">
<option value="R">Request</option>
<option value="N">Normal</option>
<option value="S">Scheduled</option>
<option value="I">Installation</option>
<option value="O">Other</option>
</select>
</td>
</tr>
<!-- RUID -->
<tr>
<td class="td-title-info">RUID</td>
<td class="td-text-info">
<input type="text" class="d_ruid">
</td>
</tr>
<!-- Driver UID -->
<tr>
<td class="td-title-info">Driver UID</td>
<td class="td-text-info">
<select class="custom-select d_driveruid">
<?php
foreach($drivers as $driver) {
echo '<option value="'.$driver['currentdriverid'].'">'.$driver['name'].'</option>';
}
?>
</select>
</td>
</tr>
</tbody>
</table>
<br>
<div class="text-center grid-layout-col-2">
<button type="button" class="btn-sub btn-order-save">SAVE</button>
<button type="button" class="btn-gray" data-dismiss="modal">CLOSE</button>
</div>
</form>
</div>
</div><!-- modal-content ends -->
</div>
</div>
<!-- ORDER DETAIL MODAL Ends -->
<!-- HISTORY MODAL-->
<div class="modal fade top map" id="map-modal-history" tabindex="-1" role="dialog">
<div id="modal-history" class="modal-dialog" role="document">
<div class="modal-content modal-content-historyOrder">
<div class="modal-header">
<h4 class="modal-title">HISTORY</h4>
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="history-warp-scroll">
<form method="POST" action="#">
<table class="tb-info-box1">
<thead>
<tr>
<td class="td-title-info td-no">No</td>
<td class="td-title-info td-date-history">Date</td>
<td class="td-title-info td-oil-quantity-history">Oil Quantity</td>
<td class="td-title-info td-date-history">Driver</td>
<td class="td-title-info td-paid">Paid (Cash)</td>
</tr>
</thead>
<tbody>
<tr>
<td class="td-text-info">85</td>
<td class="td-text-info" style="text-align: center;">2024-03-06</td>
<td class="td-text-info" style="text-align: right;margin-right: 20px;">170</td>
<td class="td-text-info" style="text-align: center;">J.L</td>
<td class="td-text-info"></td>
</tr>
<tr>
<td class="td-text-info">84</td>
<td class="td-text-info" style="text-align: center;">2024-02-21</td>
<td class="td-text-info" style="text-align: right;margin-right: 20px;">100</td>
<td class="td-text-info" style="text-align: center;">J.L</td>
<td class="td-text-info"></td>
</tr>
</tbody>
</table>
</form>
</div>
<br>
<div class="text-center">
<button type="button" class="btn-gray width-200" data-dismiss="modal">CLOSE</button>
</div>
</div>
</div>
</div>
</div>
<!-- HISTORY DETAIL MODAL Ends -->
<!-- Access Time / Closing Days 표시 스타일 (customer_detail 과 동일) -->
<link rel="stylesheet" href="/assets/css/customer_access.css">
<!-- INFO MODAL-->
<div class="modal fade top map in" id="map-modal-info" tabindex="-1" role="dialog">
<div id="modal-info" class="modal-dialog" role="document">
<div class="modal-content modal-content-modifycustomerShortInfo">
<div class="modal-header">
<h4 class="modal-title">CUSTOMER INFORMATION</h4>
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<form name="customerShortInfo" id="customerShortInfo" method="POST" novalidate="novalidate">
<input type="hidden" name="actionStr" value="SHORTINFO">
<input type="hidden" name="actionPage" value="">
<input type="hidden" name="c_uid" class="c_uid" value="">
<input type="hidden" name="c_accountno" class="c_accountno" value="">
<input type="hidden" name="mode" class="mode" value="update">
<table class="tb-info-box">
<tbody>
<tr>
<td class="td-title-info">Restaurant Name</td>
<td colspan="2" class="td-text-info shortinfo-store-name"></td>
</tr>
<tr>
<td class="td-title-info">Payment Type</td>
<td colspan="2" class="td-text-info shortinfo-payment-type"></td>
</tr>
<tr>
<td class="td-title-info">Pay Status</td>
<td colspan="2" class="td-text-info shortinfo-payment-string"></td>
</tr>
<tr>
<td class="td-title-info">Comment</td>
<td colspan="2" class="td-text-info">
<textarea id="c_comment_ri" class="textarea shortinfo-comment" name="c_comment_ri" rows="4" cols="20"></textarea>
</td>
</tr>
<tr>
<td class="td-title-info">Container Location</td>
<td colspan="2" class="td-text-info">
<textarea id="c_location" class="textarea shortinfo-container-location" name="c_location" rows="2" cols="20"></textarea>
</td>
</tr>
<!-- <tr>
<td class="td-title-info">Installation Image</td>
<td class="td-text-info" id="installImgArea" style="border-right:none;">
<span>Loading...</span>
</td>
<td style="text-align: right; border-right: 1px solid #e2e2e2; border-bottom:1px solid #e2e2e2;">
<span style="border:1px solid #72A864; border-radius:5px; padding:3px;">
<a href="#" onclick="imagepopup(jQuery('#customerShortInfo .c_uid').val());">ADD</a>
</span>
</td>
</tr> -->
</tbody>
<tbody id="mapAccessClosing"></tbody>
</table>
<br>
<div class="text-center grid-layout-col-2">
<button type="button" data-dismiss="modal" class="btn-sub" onClick="javscript:updtShortInfo();">SAVE</button>
<button type="button" class="btn-gray" data-dismiss="modal">CLOSE</button>
</div>
</form>
</div>
<div class="modal fade" id="myModalPopup" tabindex="-1" role="dialog" aria-labelledby="myModalPopup" style="opacity: 0.5; padding-right: 0px !important;">
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
<div class="modal-content" style="background-color:#2A9B56 !important; max-width:260px; margin: 0 auto;">
<div class="myModalPopup-body" style="text-align: center; border-radius: 5px; max-height:60px; font-size:18px; background-color:#2A9B56; color: #FFFFFF; font-weight: bold;padding:0.5em 1em; ">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- INFO DETAIL MODAL Ends -->
<!-- ==============================================================
Access Time / Closing Days 공용 편집 팝업 모달 (Bootstrap 3)
- 외부 PHP 를 iframe 으로 로드 (customer_detail 과 동일 팝업 재사용)
- 저장 완료 시 자식이 window.parent.closeCustomerPopup(true) 호출
============================================================== -->
<!-- ==============================================================
Access Time / Closing Days 공용 편집 팝업 (순수 오버레이)
- Bootstrap 모달을 쓰지 않음: BS3/BS5 focus-trap 충돌(무한재귀) 회피
- 외부 PHP 를 iframe 으로 로드 (customer_detail 과 동일 팝업 재사용)
- 저장 완료 시 자식이 window.parent.closeCustomerPopup(true) 호출
============================================================== -->
<div id="customerPopupOverlay" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; z-index:100000; background:rgba(0,0,0,0.5);">
<div id="customerPopupBox" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:92%; max-width:760px; background:#fff; border-radius:6px; overflow:hidden; box-shadow:0 10px 40px rgba(0,0,0,0.35);">
<div style="display:flex; justify-content:space-between; align-items:center; padding:10px 14px; background:#2A9B56; color:#fff;">
<h4 id="customerPopupTitle" style="margin:0; font-size:16px;">Edit</h4>
<button type="button" id="customerPopupClose" style="background:none; border:none; color:#fff; font-size:24px; line-height:1; cursor:pointer;">&times;</button>
</div>
<iframe id="customerPopupFrame" src="about:blank" title="Edit popup" style="width:100%; height:70vh; border:0; display:block;"></iframe>
</div>
</div>
<script>
(function () {
var popupConfig = {
accesstime: { url: '/lib/customer_access_time_popup.php', title: 'Access Time' },
closingdays: { url: '/lib/customer_closing_days_popup.php', title: 'Closing Days' }
};
var popupChanged = false;
// 클리핑/스택 방지: 오버레이를 body 직속으로 이동
jQuery('#customerPopupOverlay').appendTo(document.body);
// 현재 INFO MODAL 의 c_uid
function currentCuid() { return jQuery('#customerShortInfo .c_uid').val() || ''; }
// 표시 조각 로드 (모달 열릴 때 / 저장 후 갱신)
window.loadCustomerAccess = function (cuid) {
cuid = cuid || currentCuid();
if (!cuid) { jQuery('#mapAccessClosing').empty(); return; }
jQuery('#mapAccessClosing').load('/lib/customer_access_display.php?c_uid=' + encodeURIComponent(cuid));
};
function openPopup(key) {
var cfg = popupConfig[key];
if (!cfg) return;
var cuid = currentCuid();
if (!cuid) { alert('No customer selected.'); return; }
popupChanged = false;
jQuery('#customerPopupTitle').text(cfg.title);
jQuery('#customerPopupFrame').attr('src', cfg.url + '?c_uid=' + encodeURIComponent(cuid) + '&goStr=');
jQuery('#customerPopupOverlay').css('display', 'block');
}
function closePopup() {
jQuery('#customerPopupOverlay').css('display', 'none');
jQuery('#customerPopupFrame').attr('src', 'about:blank');
if (popupChanged) { window.loadCustomerAccess(); popupChanged = false; }
}
// 박스 클릭 → 편집 팝업 (동적 주입이라 위임 바인딩)
jQuery(document).on('click', '#mapAccessClosing .box-clickable[data-popup]', function () {
openPopup(jQuery(this).data('popup'));
});
// 닫기: X 버튼 / 배경 클릭(박스 내부 제외)
jQuery('#customerPopupClose').on('click', closePopup);
jQuery('#customerPopupOverlay').on('click', function (e) { if (e.target === this) closePopup(); });
// 자식 iframe(팝업 PHP)에서 호출하는 전역 핸들러
window.closeCustomerPopup = function (changed) {
if (changed) popupChanged = true;
closePopup();
};
})();
</script>
<!-- NOTE MODAL-->
<div class="modal fade top map" id="map-modal-note" tabindex="-1" role="dialog">
<div id="modal-note" class="modal-dialog" role="document">
<div class="modal-content modal-content-modifycustomerShortNote">
<div class="modal-header">
<h4 class="modal-title">REQUEST NOTE</h4>
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<table class="tb-info-box">
<tbody>
<tr>
<td class="td-title-info">Note</td>
<td class="td-text-info">
<textarea id="r_note" class="textarea notice-info" name="r_note" rows="4" cols="20">굳지 않았을 것 같아서 요청드리니 교체가 필요한지 확인 부탁드리겠습니다.</textarea>
</td>
</tr>
</tbody>
</table>
<br>
<div class="text-center">
<button type="button" class="btn-gray" data-dismiss="modal">CLOSE</button>
</div>
</div>
<div class="modal fade" id="myModalPopup" tabindex="-1" role="dialog" aria-labelledby="myModalPopup" style="opacity: 0.5; padding-right: 0px !important;">
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
<div class="modal-content" style="background-color:#2A9B56 !important; max-width:260px; margin: 0 auto;">
<div class="myModalPopup-body" style="text-align: center; border-radius: 5px; max-height:60px; font-size:18px; background-color:#2A9B56; color: #FFFFFF; font-weight: bold;padding:0.5em 1em; ">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- NOTE DETAIL MODAL Ends -->
<!-- SIMPLE INSTALL REQUEST MODAL -->
<div class="modal fade top map" id="map-modal-container-request" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<!-- HEADER -->
<div class="modal-header">
<h4 class="modal-title">
CONTAINER REQUEST -
<span id="simpleInstallCustomerName"></span>
</h4>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<!-- BODY -->
<div class="modal-body">
<div id="simpleInstallMeta" class="alert alert-warning py-2 px-3 mb-3" style="display:none; font-size:13px;">
<div class="fw-bold mb-2 d-flex align-items-center gap-2">
<i class="bi bi-exclamation-triangle-fill"></i>
Existing Container Request Found
</div>
<div id="simpleInstallPhotoWrap" class="mb-3" style="display:none;">
<div id="simpleInstallPhotoList" class="d-flex flex-wrap gap-2 mt-2"></div>
</div>
<div><strong>Status:</strong> <span id="siStatus"></span></div>
<div><strong>Requested by:</strong> <span id="siAddedBy"></span></div>
<div><strong>Requested at:</strong> <span id="siAddedAt"></span></div>
<div><strong>Scheduled:</strong> <span id="siScheduled"></span></div>
</div>
<!-- JOB TYPE -->
<div class="mb-3">
<label class="fw-bold">Request Type</label>
<div class="d-flex gap-2 mt-2">
<button type="button" class="btn btn-outline-primary simple-job-btn" data-job="INSTALL"> <i class="bi bi-plus-circle"></i> Add </button>
<button type="button" class="btn btn-outline-danger simple-job-btn" data-job="PICKUP"> <i class="bi bi-dash-circle"></i> Pickup </button>
<button type="button" class="btn btn-outline-warning simple-job-btn" data-job="EXCHANGE"> <i class="bi bi-arrow-left-right"></i> Exchange </button>
</div>
</div>
<!-- NOTE -->
<div class="mb-3">
<label class="fw-bold">Request Note</label>
<textarea id="simpleInstallNote" class="form-control" rows="2" placeholder="Enter request note"></textarea>
</div>
<!-- PHOTO -->
<div class="mb-3">
<label class="fw-bold">Photos</label>
<input type="file" id="simpleInstallPhotos" class="form-control" multiple accept="image/*">
</div>
<!-- HIDDEN DATA -->
<input type="hidden" id="simpleInstallCustomerUid">
<input type="hidden" id="simpleInstallDriverUid">
<!-- FOOTER BUTTON -->
<div class="text-center mt-4">
<button type="button" id="btnSimpleInstallDelete" class="btn btn-danger" style="display:none;">Delete Request</button>
<button type="button" id="btnSimpleInstallSubmit" class="btn btn-success" disabled>Submit Request</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="myModalPopup" tabindex="-1" role="dialog" aria-labelledby="myModalPopup" style="opacity: 0.5; padding-right: 0px !important;">
<div class="modal-dialog modal-dialog-centered modal-xl" role="document">
<div class="modal-content" style="background-color:#2A9B56 !important; max-width:260px; margin: 0 auto;">
<div class="myModalPopup-body" style="text-align: center; border-radius: 5px; max-height:60px; font-size:18px; background-color:#2A9B56; color: #FFFFFF; font-weight: bold;padding:0.5em 1em; ">
</div>
</div>
</div>
</div>
</div>
<!-- image modal -->
<script src="/js/customer_image.js?v=<?=time()?>"></script>
<div class="modal fade top map" id="customerImageModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content" style="position: relative;">
<div class="modal-header">
<h4 class="modal-title">
CUSTOMER IMAGES - <span id="imageCustomerName"></span>
</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div style="position: absolute; top: 60px; left: 15px; z-index: 1060;">
<button type="button"
id="btn-modal-upload-action"
onclick="openCustomerImageUploadDirect(this.getAttribute('data-cuid'));"
style="display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 65px;
height: 65px;
background: rgba(255, 140, 46, 0.7);
color: white;
border: 2px solid rgba(255, 255, 255, 0.5);
border-radius: 50%;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
cursor: pointer;
transition: transform 0.1s;">
<i class="bi bi-camera-fill" style="font-size: 24px;"></i>
<span style="font-size: 9px; font-weight: bold; margin-top: -2px;">ADD</span>
</button>
</div>
<div class="modal-body" id="customerImageModalBody" style="padding-top: 40px;">
<div class="text-center text-muted py-5">
Loading images...
</div>
</div>
</div>
</div>
</div>
<div class="container">
<!-- Modal -->
<div class="modal fade" id="myModal" data-backdrop="static" data-keyboard="false" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<!--button type="button" class="close" data-dismiss="modal">&times;</button-->
<h4>MESSAGE</h4>
</div>
<div class="modal-body">
<p> </p>
</div>
<div class="modal-footer">
<button type="button" class="modal-close" data-dismiss="modal" >OK</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade top map" id="modal-confirm" tabindex="-1" role="dialog">
<div id="modal-input" class="modal-dialog" role="document">
<!--
<div class="modal-content">
<div class="modal-body">
<p>Are you sure to remove order?</p>
</div>
<div class="modal-footer">
<input type="hidden" class="order-date" value="">
<input type="hidden" class="id" value="">
<input type="hidden" class="c_index" value="">
<button type="button" class="btnRemoveConfirm" onClick="javascript:confirmRemoveOrder();">Confirm</button>
<button type="button" class="btn-gray" data-dismiss="modal" >Cancel</button>
</div>
</div>--><!-- modal-content ends -->
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Confirm</h4>
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<input type="hidden" class="order-date" value="">
<input type="hidden" class="id" value="">
<input type="hidden" class="duid" value="">
<input type="hidden" class="c_index" value="">
<input type="hidden" class="accountno" value="">
<p>Are you sure to remove order?</p>
<br>
<div class="text-center grid-layout-col-2">
<button type="button" class="btn-sub" onClick="javascript:confirmRemoveOrder();" data-dismiss="modal">Confirm</button>
<button type="button" class="btn-gray" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
</div>
<!-- ORDER DETAIL MODAL Ends -->
<!-- Modal -->
<input type="hidden" id="routeplanner_total_duration">
<input type="hidden" id="routeplanner_total_distance">
<input type="hidden" id="routeplanner_encoded_path">
<a href="#" class="scroll-top d-flex align-items-center justify-content-center"><i class="bi bi-arrow-up-short"></i></a>
<div id="preloader"></div>
<p></p><p></p><p></p>
<div class="route-planner-waypoints">
<table>
<thead>
<tr>
<th>No</th>
<th>Name</th>
<th>Account</th>
<th>Container</th>
<th>Payment</th>
<th>Rate</th>
<th>Address</th>
<th>City</th>
<th>Last</th>
<th>Forecast</th>
<th>Actual(L)</th>
<th>Last Paid</th>
<th>2023</th>
<th>2024</th>
<th>Cycle</th>
<th>Last PU</th>
<th>Km</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</body>
</html>
<script>
const SS_UID = <?= (int)($_SESSION['ss_UID'] ?? 0) ?>;
const SS_DRUID = <?= (int)($_SESSION['ss_DRUID'] ?? 0) ?>;
var center = { lat: 43.732188, lng: -79.571618 };
var currentLocation = { lat: 43.764693323038315, lon: -79.47762958134186 };
/* $(window).ready(function(){
jQuery("#driver").change(function(e){
let data = {
"driver" : jQuery("#driver").val()
}
api('inqDriverGeo', data, rstDriverGeo);
});
var rstDriverGeo = function(json) {
if (json.result['geolat']) {
var driverlat = json.result['geolat'];
var driverlon = json.result['geolon'];
}
else {
var driverlat = 43.732188;
var driverlon = -79.571618;
}
alert (driverlat);
center = { lat: driverlat, lng: driverlon };
}
});
*/
var map;
var directionsRenderer;
var currentMarker;
const {AdvancedMarkerElement} = google.maps.importLibrary("marker");
$(window).ready(function(){
<? if ($_SESSION['ss_LEVEL'] == 9) { ?>
$("#driver").val("<?=$_SESSION['ss_DRUID']?>").change();
<? } ?>
initMap();
/*Redraw marker when search option changed*/
jQuery("#orderdate").change(function(e){
try { sessionStorage.setItem('map_orderdate', jQuery(this).val()); } catch(err) {}
jQuery('.qty').prop('checked', false);
jQuery("#search-keyword").val("");
drawPoint();
let pickupdata = {
"route_date" : jQuery("#orderdate").val(),
"driver" : jQuery("#driver").val()
}
api('inqPickupQty', pickupdata, rstInqPickupQty);
});
jQuery(".chk-ordered").click(function(e){
drawPoint();
});
jQuery("#driver").change(function(e){
try { sessionStorage.setItem('map_driver', jQuery(this).val()); } catch(err) {}
jQuery('.qty').prop('checked', false);
jQuery("#search-keyword").val("");
drawPoint();
//map ceneter change
let data = {
"route_date" : jQuery("#orderdate").val(),
"driver" : jQuery("#driver").val()
}
api('inqMapCenter', data, rstInqMapCenter);
api('inqPickupQty', data, rstInqPickupQty);
});
// 선택한 날짜/드라이버 복원 (F5·INIT 리로드 후 유지)
(function(){
var changed = false;
try {
var savedDate = sessionStorage.getItem('map_orderdate');
if (savedDate) { jQuery('#orderdate').val(savedDate); changed = true; }
<? if ($_SESSION['ss_LEVEL'] != 9) { ?>
var savedDriver = sessionStorage.getItem('map_driver');
if (savedDriver !== null && jQuery('#driver option[value="'+savedDriver+'"]').length) {
jQuery('#driver').val(savedDriver); changed = true;
}
<? } ?>
} catch(err) {}
if (changed) jQuery('#driver').trigger('change');
})();
jQuery("#search-keyword").keyup(function(e){
//if(jQuery(e.target).val().length > 0) {
drawPoint();
//}
});
jQuery(".qty").click(function(e){
drawPoint();
});
jQuery(".qty-min").change(function(e){
let index = jQuery(".qty-min").index(e.target);
if(parseInt(jQuery(e.target).val()) > jQuery(".qty-max:eq("+(index-1)+")").val()) {
for(let i=index;i < jQuery(".qty-min").length;i++) {
let unit = 49;
if(jQuery(".qty-min:eq("+i+")").val() > 200){
unit = 99;
}
if((i+1) < jQuery(".qty-min").length){
jQuery(".qty-max:eq("+i+")").val(parseInt(jQuery(".qty-min:eq("+i+")").val()) + unit);
}
jQuery(".qty-min:eq("+(i+1)+")").val(parseInt(jQuery(".qty-max:eq("+i+")").val()) + 1);
}
drawPoint();
}else{
jQuery(e.target).val(parseInt(jQuery(".qty-max:eq("+(index-1)+")").val())+1);
}
});
jQuery(".qty-max").change(function(e){
let index = jQuery(".qty-max").index(e.target);
if(parseInt(jQuery(e.target).val()) > jQuery(".qty-min:eq("+index+")").val()) {
jQuery(".qty-min:eq("+(index+1)+")").val(parseInt(jQuery(e.target).val())+1).trigger('change');
drawPoint();
}else{
jQuery(e.target).val(parseInt(jQuery(".qty-min:eq("+(index+1)+")").val()) - 1);
}
});
jQuery(".btn-current").click(function(e){
inqCurrent();
});
jQuery(".btn-routeplanner").click(function(e){
jQuery("#routePlannerModal #addressList").html('<p class="text-center">Loading...</p>');
// 각 드라이버의 data 를 가져오는 문제가 있어서 강제로 회사 주소로 셋팅 (2025.06.16)
currentLocation = { lat: 43.764693323038315, lon: -79.47762958134186 };
//alert(currentLocation['lat']);
let data = {
"route_date" : jQuery("#orderdate").val(),
"m_lat" : currentLocation['lat'],
"m_lon" : currentLocation['lon'],
"driver" : jQuery("#driver").val()
}
api('inqRouteAddress', data, rstInqRouteAddress);
});
jQuery(".routePlannerApply").click(function(e){
removeCircle();
for(let j=0; j < marker.length; j++) {
marker[j].setMap(null);
marker[j] = null;
}
marker = [];
overlay = [];
let waypoints = [];
let dispWaypoints = [];
for(let i=0; i < jQuery("#routePlannerModal #addressList .draggable-item").length; i++) {
let dataArr = jQuery("#routePlannerModal #addressList .draggable-item:eq("+i+")").attr("data").split("*&^&*");
if(waypoints.length < 24) {
if(i < (jQuery("#routePlannerModal #addressList .draggable-item").length - 1)){
let point = {
'location' : {
'lat' : parseFloat(dataArr[0]),
'lng' : parseFloat(dataArr[1])
},
stopover : true
}
waypoints.push(point);
}
}
// Print
let dispWaypoint = {
'point' : (i < 25)?String.fromCharCode(65+(i+1)):'',
'name' : dataArr[2],
'address' : dataArr[3],
'accountno' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .account").val(),
'maincontainer' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .maincontainer").val(),
'paymenttype' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .paymenttype").val(),
'rate' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .rate").val(),
'city' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .city").val(),
'lastpickupquantity' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .lastpickupquantity").val(),
'estquantity' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .estquantity").val(),
'quantity' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .quantity").val(),
'lastpaiddate' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .lastpaiddate").val(),
'this_year' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .this_year").val(),
'last_year' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .last_year").val(),
'fullcycle' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .fullcycle").val(),
'lastpickupdate' : jQuery("#routePlannerModal #addressInformationList .item-information-"+dataArr[4]+" .lastpickupdate").val()
}
dispWaypoints.push(dispWaypoint);
}
let destinationDataArr = jQuery("#routePlannerModal #addressList .draggable-item:eq("+(jQuery("#routePlannerModal #addressList .draggable-item").length - 1)+")").attr("data").split("*&^&*");
if(jQuery("#routePlannerModal #addressList .draggable-item").length > 23) {
destinationDataArr = jQuery("#routePlannerModal #addressList .draggable-item:eq(23)").attr("data").split("*&^&*");
}
let directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer({
map: map,
suppressMarkers: false
});
directionsService.route({
origin: {lat: parseFloat(currentLocation['lat']), lng: parseFloat(currentLocation['lon'])}, // 시작 위치
destination: {lat: parseFloat(destinationDataArr[0]), lng: parseFloat(destinationDataArr[1])}, // 종료 위치 (시작 위치와 같을 수 있음)
waypoints: waypoints,
optimizeWaypoints: true, // Google에게 경유지 최적화를 요청
travelMode: google.maps.TravelMode.DRIVING,
}, (response, status) => {
if (status === 'OK') {
directionsRenderer.setDirections(response);
var route = response.routes[0];
var totalDuration = 0;
var totalDistance = 0;
let waypointsTable = '<tr><td>A</td><td>Current Location</td><td colspan="13">&nbsp;</td></tr>';
for (var i = 0; i < route.legs.length; i++) {
totalDuration += route.legs[i].duration.value;
totalDistance += route.legs[i].distance.value;
/* suppressMarkers: true
var marker = new google.maps.Marker({
position: route.legs[i].start_location,
label: ""+(i+1),
map: map
});
*/
if(i != 0) {
waypointsTable += '<tr>';
waypointsTable += '<td>'+dispWaypoints[i-1]['point']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['name']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['accountno']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['maincontainer']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['paymenttype']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['rate']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['address']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['city']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['lastpickupquantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['estquantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['quantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['lastpaiddate']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['this_year']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['last_year']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['fullcycle']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i-1]['lastpickupdate']+'</td>';
waypointsTable += '<td>'+(route.legs[i-1].distance.value/1000)+'</td>';
waypointsTable += '</tr>';
}
}
// Convert the duration to minutes
totalDuration = totalDuration / 60;
// 총 이동 거리를 킬로미터 단위로 변환 (미터 단위에서 킬로미터 단위로 변환하기 위해 1000으로 나눔)
totalDistance = totalDistance / 1000;
jQuery("#routeplanner_total_duration").val(totalDuration);
jQuery("#routeplanner_total_distance").val(totalDistance);
waypointsTable += '<tr>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['point']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['name']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['accountno']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['maincontainer']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['paymenttype']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['rate']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['address']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['city']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['lastpickupquantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['estquantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['quantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['lastpaiddate']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['this_year']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['last_year']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['fullcycle']+'</td>';
waypointsTable += '<td>'+dispWaypoints[route.legs.length-1]['lastpickupdate']+'</td>';
waypointsTable += '<td>'+(route.legs[route.legs.length-1].distance.value/1000)+'</td>';
waypointsTable += '</tr>';
if(dispWaypoints.length > route.legs.length) {
for (let i=route.legs.length;i < dispWaypoints.length;i++) {
waypointsTable += '<tr>';
waypointsTable += '<td>'+dispWaypoints[i]['point']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['name']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['accountno']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['maincontainer']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['paymenttype']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['rate']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['address']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['city']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['lastpickupquantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['estquantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['quantity']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['lastpaiddate']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['this_year']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['last_year']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['fullcycle']+'</td>';
waypointsTable += '<td>'+dispWaypoints[i]['lastpickupdate']+'</td>';
waypointsTable += '<td></td>';
waypointsTable += '</tr>';
}
}
jQuery(".route-planner-waypoints table tbody").html(waypointsTable);
/*
//overlay 텍스트 마커 생성
let waypointCnt = 0;
waypoints.forEach(waypoint => {
let labelArr = jQuery("#routePlannerModal #addressList .draggable-item:eq("+waypointCnt+")").attr("data").split("/");
let canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 50;
let context = canvas.getContext('2d');
// 배경 그리기
context.fillStyle = '#FF6347'; // 배경색
context.fillRect(0, 0, canvas.width, canvas.height);
// 텍스트 그리기
context.fillStyle = '#FFFFFF'; // 텍스트 색상
context.font = '12px Arial';
context.fillText(labelArr[2], 10, 25); // 텍스트와 위치
let markerIcon = canvas.toDataURL(); // 캔버스를 데이터 URL로 변환
new google.maps.Marker({
position: waypoint.location,
icon: markerIcon,
map: map,
});
waypointCnt += 1;
});*/
jQuery(".btn-route-print").removeClass("hidden");
} else {
window.alert('Directions request failed due to ' + status);
}
});
jQuery("#routePlannerModal .close").trigger("click");
});
jQuery(".btn-route-print").click(function(e){
window.print();
});
jQuery(".btn-order-save").click(function(e){
// 폼 데이터 유효성 검사
if (addOrderValidation()) {
let data = {
"orderdate" : jQuery("#map-modal-order .d_orderdate").val(),
"ordertype" : jQuery("#map-modal-order .d_ordertype").val(),
"ruid" : jQuery("#map-modal-order .d_ruid").val(),
"driveruid" : jQuery("#map-modal-order .d_driveruid").val(),
"customer_uid" : jQuery("#map-modal-order .d_customeruid").val(),
"customer_no" : jQuery("#map-modal-order .d_customerno").val(),
"c_index" : jQuery("#map-modal-order .c_index").val()
}
api('addOrder', data, rstAddOrder);
}
});
jQuery(".btn-input-save").click(function(e){
// 폼 데이터 유효성 검사
if (saveInputValidation()) {
let data = {
"uid" : jQuery("#map-modal-input .d_uid").val(),
"customeruid" : jQuery("#map-modal-input .d_customeruid").val(),
"customerno" : jQuery("#map-modal-input .d_customerno").val(),
"orderdate" : jQuery("#map-modal-input .d_orderdate").val(),
//"ordertype" : jQuery("#map-modal-input .d_ordertype").val(),
//"ruid" : jQuery("#map-modal-input .d_ruid").val(),
"visitdate" : replaceAll(jQuery("#map-modal-input .d_visitdate").val(), '-' ,''),
"driveruid" : jQuery("#map-modal-input .d_driveruid").val(),
"druid" : jQuery("#map-modal-input .d_druid").val(),
"quantity" : jQuery("#map-modal-input .d_quantity").val(),
"sludge" : jQuery("#map-modal-input .d_sludge").val(),
"note" : jQuery("#map-modal-input .d_note").val(),
"createruid" : <?=$_SESSION['ss_UID']?>,
"paymenttype" : jQuery("#map-modal-input .d_paymenttype").val(),
"payamount" : jQuery("#map-modal-input .d_payamount").val(),
"payeename" : jQuery("#map-modal-input .d_payeename").val(),
"paystatus" : jQuery("#map-modal-input input[name='radio-group']:checked").val()
}
// api('saveInput', data, rstSaveInput);
api('saveInput', data, function(rst){
rstSaveInput(rst);
drawPoint(); // 저장시 깃발 색깔 변경
});
//let pickupdata = {
// "route_date" : jQuery("#orderdate").val(),
// "driver" : jQuery("#driver").val()
//}
//api('inqPickupQty', pickupdata, rstInqPickupQty);
}
});
});
function updatePickupQty(){
let pickupdata = {
"route_date" : jQuery("#orderdate").val(),
"driver" : jQuery("#driver").val()
}
api('inqPickupQty', pickupdata, rstInqPickupQty);
}
function smoothZoom(map, maxZoom, currentZoom) {
if (currentZoom >= maxZoom) {
return;
} else {
google.maps.event.addListenerOnce(map, 'zoom_changed', function(event) {
smoothZoom(map, maxZoom, currentZoom + 1);
});
setTimeout(function() {
map.setZoom(currentZoom);
}, 80); // Adjust the speed of zooming
}
}
var rstInqPickupQty = function(json) {
jQuery("#targetquantity").text(json.curTargetQty);
jQuery("#pickupquantity").text(json.curPickupQty);
jQuery("#pickupvisit").text(json.curVisitCnt);
jQuery("#targetvisit").text(json.curTargetVisitCnt);
setPickupGauge(json.curPickupQty, json.curTargetQty);
}
// Pickup Qty 버튼을 타겟 대비 비율만큼 파란색으로 채움
function setPickupGauge(pickup, target){
var p = parseInt(String(pickup).replace(/[^0-9]/g, ''), 10) || 0;
var t = parseInt(String(target).replace(/[^0-9]/g, ''), 10) || 0;
var pct = (t > 0) ? Math.round(p / t * 100) : 0; // 실제 퍼센트 (초과 시 100+ 그대로)
var ratio = Math.min(100, pct); // 채움 비율 (100% 상한)
jQuery("#pickupQtyBtn").css(
"background",
"linear-gradient(to right, #007bff 0%, #007bff " + ratio + "%, #a9cdff " + ratio + "%, #a9cdff 100%)"
);
jQuery("#pickuppercent").text(t > 0 ? pct + "% · " : "");
// 타겟 100% 도달 시 폭죽 (도달하는 순간 1회, 미만으로 떨어지면 재무장)
var reached = (t > 0 && pct >= 100);
if (reached && !window._pickupGoalReached) {
launchFireworks();
}
window._pickupGoalReached = reached;
}
// 미리 만든 글로우 스프라이트 캐시 (shadowBlur 대체 → 모바일에서 빠름)
var _fwSprites = {};
function fwSprite(color){
if (_fwSprites[color]) return _fwSprites[color];
var R = 16;
var s = document.createElement('canvas');
s.width = s.height = R * 2;
var g = s.getContext('2d');
var grad = g.createRadialGradient(R, R, 0, R, R, R);
grad.addColorStop(0, color);
grad.addColorStop(0.35, color);
grad.addColorStop(1, 'rgba(0,0,0,0)');
g.fillStyle = grad;
g.beginPath(); g.arc(R, R, R, 0, Math.PI*2); g.fill();
_fwSprites[color] = s;
return s;
}
// 폭죽 애니메이션 (canvas, 자체 제거) - 로켓 발사 + 글로우(스프라이트)
function launchFireworks(){
// 모션 최소화 설정이면 생략
if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var canvas = document.createElement('canvas');
canvas.style.cssText = 'position:fixed;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:99999;';
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
var W = canvas.width = window.innerWidth;
var H = canvas.height = window.innerHeight;
// 저사양(터치/작은 화면) 감지 → 개수 감소
var lowPower = (window.matchMedia && matchMedia('(pointer: coarse)').matches) || Math.min(W, H) < 760;
var NUM_ROCKETS = lowPower ? 7 : 12;
var P_MIN = lowPower ? 28 : 60;
var P_RANGE = lowPower ? 26 : 50;
var LAUNCH_GAP = lowPower ? 300 : 230;
var colors = ['#ff3b3b','#ffd23f','#3bd16f','#3b9dff','#c14bff','#ff8c1a','#ff5fa2','#ffffff'];
var rockets = [];
var particles = [];
function rand(a, b){ return a + Math.random()*(b - a); }
function pick(){ return colors[Math.floor(Math.random()*colors.length)]; }
function launchRocket(){
rockets.push({
x: rand(W*0.15, W*0.85),
y: H + 10,
vy: -rand(9, 13),
targetY: rand(H*0.10, H*0.42),
color: pick()
});
}
function explode(x, y, color){
var n = P_MIN + Math.floor(Math.random()*P_RANGE);
var base = rand(3, 7);
for(var i=0;i<n;i++){
var angle = Math.random()*Math.PI*2;
var speed = base * (0.3 + Math.random());
particles.push({
x:x, y:y,
vx:Math.cos(angle)*speed,
vy:Math.sin(angle)*speed,
life:1,
decay: rand(0.008, 0.018),
size: rand(1.5, 3.5),
color: Math.random() < 0.18 ? '#ffffff' : color
});
}
}
// 중앙 배너 팝업
var banner = document.createElement('div');
banner.textContent = '🎉 TARGET 100%! 🎉';
banner.style.cssText = 'position:fixed;left:50%;top:24%;transform:translate(-50%,-50%);'
+ 'font-size:38px;font-weight:800;color:#fff;text-shadow:0 2px 12px rgba(0,0,0,0.6),0 0 22px #ffd23f;'
+ 'z-index:100000;pointer-events:none;white-space:nowrap;';
document.body.appendChild(banner);
if (banner.animate) {
banner.animate([
{ opacity:0, transform:'translate(-50%,-50%) scale(0.5)' },
{ opacity:1, transform:'translate(-50%,-50%) scale(1.15)', offset:0.2 },
{ opacity:1, transform:'translate(-50%,-50%) scale(1)', offset:0.8 },
{ opacity:0, transform:'translate(-50%,-50%) scale(1.25)' }
], { duration:3200, easing:'ease-out' }).onfinish = function(){ banner.remove(); };
} else {
setTimeout(function(){ banner.remove(); }, 3200);
}
// 순차 발사
for(var s=0;s<NUM_ROCKETS;s++){
(function(delay){ setTimeout(launchRocket, delay); })(s*LAUNCH_GAP);
}
var start = performance.now();
var DURATION = 3500;
function frame(now){
var elapsed = now - start;
ctx.clearRect(0, 0, W, H);
ctx.globalCompositeOperation = 'lighter'; // 겹칠수록 밝게 → 글로우
// 로켓
for(var i=rockets.length-1;i>=0;i--){
var r = rockets[i];
r.y += r.vy;
var rs = fwSprite(r.color);
ctx.globalAlpha = 1;
ctx.drawImage(rs, r.x-7, r.y-7, 14, 14);
ctx.globalAlpha = 0.35;
ctx.drawImage(rs, r.x-5, r.y+4, 10, 10); // 꼬리
if(r.y <= r.targetY){ explode(r.x, r.y, r.color); rockets.splice(i,1); }
}
// 파티클
for(var j=particles.length-1;j>=0;j--){
var p = particles[j];
p.vy += 0.04; // 중력
p.vx *= 0.985; p.vy *= 0.985;
p.x += p.vx; p.y += p.vy;
p.life -= p.decay;
if(p.life<=0){ particles.splice(j,1); continue; }
var d = p.size * (0.6 + p.life*0.6) * 3; // 스프라이트 표시 크기
ctx.globalAlpha = Math.max(0, p.life);
ctx.drawImage(fwSprite(p.color), p.x - d/2, p.y - d/2, d, d);
}
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
if(elapsed < DURATION || rockets.length > 0 || particles.length > 0){
requestAnimationFrame(frame);
} else if(canvas.parentNode){
canvas.parentNode.removeChild(canvas);
}
}
requestAnimationFrame(frame);
}
jQuery(function(){
setPickupGauge(jQuery("#pickupquantity").text(), jQuery("#targetquantity").text());
});
var rstSaveInput = function(json) {
$('#map-modal-input').modal('hide'); // Bootstrap 정상 닫기 시도
$('#map-modal-input').removeClass('show in').css('display', 'none').attr('aria-hidden', 'true'); // 남아 있을 수 있는 클래스 제거
$('.modal-backdrop').remove(); // backdrop 제거
showPopupMessage(json.msg);
//drawPoint();
updatePickupQty();
}
function replaceAll(str, searchStr, replaceStr) {
return str.split(searchStr).join(replaceStr);
}
function initAddOrder(customer_id, index){
//jQuery("#map-modal-order .d_customeruid").val(customer_id);
//jQuery("#map-modal-order .c_index").val(index);
//jQuery("#map-modal-order .d_orderdate").val(jQuery("#orderdate").val());
let data = {
"orderdate" : jQuery("#orderdate").val(),
"driveruid" : jQuery("#driver").val(),
"customer_uid" : customer_id,
"d_createruid" : <?=$_SESSION['ss_UID']?>,
"c_index" : index
}
api('addOrderDirect', data, rstAddOrder);
}
var rstAddOrder = function(json) {
//$('#map-modal-order .close').trigger('click');
//jQuery(".modal").removeClass("show");
//jQuery(".modal-backdrop").remove();
if (json.c_return == 0) {
jQuery(".addOrderBtn_"+json.c_index).addClass('hidden');
jQuery(".removeOrderBtn_"+json.c_index).removeClass('hidden');
showPopupMessage(json.msg);
} else {
jQuery(".addOrderBtn_"+json.c_index).addClass('hidden');
jQuery(".removeOrderBtn_"+json.c_index).removeClass('hidden');
showPopupMessage(json.msg);
drawPoint(); // Add order시 flag 가 변경되지 않으므로 필요함
}
}
function showPopupMessage(msg) {
jQuery("#popupMessage").text(msg);
$('#popupMessage').fadeIn().delay(2000).fadeOut();
}
function removeOrder(id, duid, index, accountno) {
jQuery("#modal-confirm .order-date").val(jQuery("#orderdate").val());
jQuery("#modal-confirm .id").val(id);
jQuery("#modal-confirm .duid").val(duid);
jQuery("#modal-confirm .c_index").val(index);
jQuery("#modal-confirm .accountno").val(accountno);
/*
if(confirm('Are you sure you want to remove the order?')) {
let data = {
"orderdate" : jQuery("#orderdate").val(),
"id" : id,
"c_index" : index
}
api('removeOrder', data, rstRemoveOrder);
}*/
}
function confirmRemoveOrder(){
let data = {
"orderdate" : jQuery("#modal-confirm .order-date").val(),
"id" : jQuery("#modal-confirm .id").val(),
"duid" : jQuery("#modal-confirm .duid").val(),
"c_index" : jQuery("#modal-confirm .c_index").val(),
"accountno" : jQuery("#modal-confirm .accountno").val(),
"createruid" : <?=$_SESSION['ss_UID']?>
}
api('removeOrder', data, rstRemoveOrder);
//let pickupdata = {
// "route_date" : jQuery("#orderdate").val(),
// "driver" : jQuery("#driver").val()
//}
//api('inqPickupQty', pickupdata, rstInqPickupQty);
}
var rstRemoveOrder = function(json) {
showPopupMessage(json.msg);
jQuery(".addOrderBtn_"+json.c_index).removeClass('hidden');
jQuery(".removeOrderBtn_"+json.c_index).addClass('hidden');
drawPoint(); // remove order시 flag 가 변경되지 않으므로 필요함
updatePickupQty();
}
function addOrderValidation(){
jQuery("#map-modal-order .d_ruid").css("border","1px solid black");
if(jQuery("#map-modal-order .d_ruid").val().trim() === '') {
jQuery("#map-modal-order .d_ruid").css("border","1px solid red");
return false;
}
return true;
}
function initInput(customer_id, customer_no){
// input modal 에서 저장버튼 클릭 후 다시 오픈할 때, modal 내부에 포커스가 남아있어서 modal 이 오픈되지 않는 이슈 (modal-backdrop 만 열려서 전체 클릭이 막힘)
// 1. 현재 포커스를 가진 요소에서 포커스를 제거하고 body에 포커스
jQuery(':focus').blur();
jQuery('body').focus();
// 2. 모달 자체의 속성 초기화 (Bootstrap의 동작을 돕기 위해 유지)
jQuery('#map-modal-input').attr('aria-hidden', 'false').removeAttr('tabindex');
// end input modal
let loadingTxt = 'Loading...';
jQuery("#map-modal-input .shortinput-store-name").text(loadingTxt);
// =슬러지 에러메세지 초기화=====================================================
jQuery(".qty-error, .sludge-error").hide();
jQuery("#map-modal-input .d_quantity, #map-modal-input .d_sludge").css("border", "1px solid #ced4da");
// ===========================================================================
jQuery("#map-modal-input .d_uid, #map-modal-input .d_customeruid, #map-modal-input .d_customerno, #map-modal-input .orderdate, #map-modal-input .d-ruid, #map-modal-input .d_visitdate, #map-modal-input .d_quantity, #map-modal-input .d_sludge, #map-modal-input .d_note").val('');
jQuery("#map-modal-input .d_payamount, #map-modal-input .d_payeename").val('');
jQuery("#map-modal-input .d_customeruid").val(customer_id);
jQuery("#map-modal-input .d_customerno").val(customer_no);
jQuery("#map-modal-input .d_paystatusunpaid").prop("checked", true);
jQuery("#map-modal-input .d_orderdate").val(jQuery("#orderdate").val());
<? if ($_SESSION['ss_LEVEL'] == 9) { ?>
jQuery("#map-modal-input .d_visitdate").prop("readonly", true);
let orderdateSTR = jQuery("#orderdate").val();
let orderdateRT = orderdateSTR.replaceAll("-","");
//alert(orderdateRT);
if(orderdateRT < <?=date("Ymd")?>) {
jQuery("#map-modal-input .btn-input-save").prop("disabled", true);
}else {
jQuery("#map-modal-input .btn-input-save").prop("disabled", false);
}
<? } ?>
let data = {
"c_uid" : customer_id,
"orderdate" : jQuery("#orderdate").val()
}
api('initInput', data, rstInitInput);
}
var rstInitInput = function(json) {
jQuery("#map-modal-input .shortinput-store-name").text(json.result['d_name']);
jQuery("#map-modal-input .shortinput-rate").text(json.result['d_rate']);
jQuery("#map-modal-input .d_driveruid").val(json.result['d_driveruid']);
jQuery("#map-modal-input .d_druid").val(json.result['d_druid']);
jQuery("#map-modal-input .d_paymenttype").val(json.result['d_paymenttype']);
jQuery("#map-modal-input .d_sludge").val(json.result['d_sludge']);
if(json.result['d_uid'].length > 0) {
jQuery("#map-modal-input .d_uid").val(json.result['d_uid']);
jQuery("#map-modal-input .d_quantity").val(json.result['d_quantity']);
jQuery("#map-modal-input .d_note").val(json.result['d_note']);
if(json.result['d_paymenttype'] === 'CA') {
jQuery("#map-modal-input .CA_option").removeClass("hide");
jQuery("#map-modal-input .d_payamount").val(json.result['d_payamount']);
jQuery("#map-modal-input .d_payeename").val(json.result['d_payeename']);
if(json.result['d_paystatus'] === 'P') {
jQuery("#map-modal-input .d_paystatuspaid").prop("checked", true);
jQuery("#map-modal-input .d_paystatusunpaid").prop("unchecked", true);
} else if (json.result['d_paystatus'] === 'N') {
jQuery("#map-modal-input .d_paystatuspaid").prop("unchecked", true);
jQuery("#map-modal-input .d_paystatusunpaid").prop("checked", true);
}
} else{
jQuery("#map-modal-input .CA_option").addClass("hide");
}
}else{
if(json.result['d_paymenttype'] === 'CA') {
jQuery("#map-modal-input .CA_option").removeClass("hide");
}else{
jQuery("#map-modal-input .CA_option").addClass("hide");
}
}
//alert (jQuery("#map-modal-input .d_uid").val());
//alert (jQuery("#map-modal-input .d_paymenttype").val());
//visitdate setting
let visitdate = json.result['d_orderdate'];
//jQuery("#map-modal-input .d_visitdate").val(visitdate.substring(0, 4) + '-' + visitdate.substring(4, 6) + '-' + visitdate.substring(6, 8) + 'T' + visitdate.substring(8, 10) + ':' + visitdate.substring(10, 12));
jQuery("#map-modal-input .d_visitdate").val(visitdate.substring(0, 4) + '-' + visitdate.substring(4, 6) + '-' + visitdate.substring(6, 8));
}
function saveInputValidation(){
// 1. 초기화 (테두리 원복 및 에러메시지 숨김)
jQuery("#map-modal-input .d_quantity, #map-modal-input .d_sludge").css("border", "1px solid black");
jQuery(".qty-error, .sludge-error").hide();
// 2. 수량(Quantity) 검사
if(jQuery("#map-modal-input .d_quantity").val().trim() === '') {
jQuery("#map-modal-input .d_quantity").css("border", "1px solid red");
jQuery(".qty-error").show(); // 수량 에러 문구 표시
jQuery("#map-modal-input .d_quantity").focus();
return false;
}
// 3. 슬러지(Sludge) 검사
if(jQuery("#map-modal-input .d_sludge").val().trim() === '') {
// .number-input 박스 자체나 input에 빨간 테두리
jQuery("#map-modal-input .d_sludge").css("border", "1px solid red");
jQuery(".sludge-error").show(); // 슬러지 에러 문구 표시
jQuery("#map-modal-input .d_sludge").focus();
return false;
}
return true;
}
// 경로를 지도에서 제거하는 함수
function clearRoute() {
if (directionsRenderer) {
//directionsRenderer.setDirections({routes: []}); // 경로 데이터를 비우기
// 또는
directionsRenderer.setMap(null); // 지도에서 경로 제거
}
}
var rstInqRouteAddress = function(json) {
let output = '';
let tableData = '';
for(let i=0; i < json.result.length; i++) {
output += '<div class="draggable-item" draggable="true" id="visit_'+i+'" data="'+json.result[i]['lat']+'*&^&*'+json.result[i]['lon']+'*&^&*'+json.result[i]['name']+'*&^&*'+json.result[i]['address']+'*&^&*'+i+'">';
output += '<table>';
output += '<tr>';
output += '<td>'+(i+1)+'</td>';
output += '<td><b>'+json.result[i]['name']+'</b></td>';
output += '<td>'+json.result[i]['address']+', '+json.result[i]['city']+', '+json.result[i]['postal']+'</td>';
output += '</tr>';
output += '</table>';
output += '</div>';
tableData += '<div class="item-information-'+i+'">';
tableData += '<input type="text" class="account" value="'+json.result[i]['accountno']+'">';
tableData += '<input type="text" class="maincontainer" value="'+json.result[i]['maincontainer']+'">';
tableData += '<input type="text" class="paymenttype" value="'+json.result[i]['paymenttype']+'">';
tableData += '<input type="text" class="rate" value="'+json.result[i]['rate']+'">';
tableData += '<input type="text" class="address" value="'+json.result[i]['address']+'">';
tableData += '<input type="text" class="city" value="'+json.result[i]['city']+'">';
tableData += '<input type="text" class="lastpickupquantity" value="'+json.result[i]['lastpickupquantity']+'">';
tableData += '<input type="text" class="estquantity" value="'+json.result[i]['estquantity']+'">';
tableData += '<input type="text" class="quantity" value="'+json.result[i]['quantity']+'">';
tableData += '<input type="text" class="lastpaiddate" value="'+json.result[i]['lastpaiddate']+'">';
tableData += '<input type="text" class="this_year" value="'+json.result[i]['THIS_YEAR']+'">';
tableData += '<input type="text" class="last_year" value="'+json.result[i]['LAST_YEAR']+'">';
tableData += '<input type="text" class="fullcycle" value="'+json.result[i]['fullcycle']+'">';
tableData += '<input type="text" class="lastpickupdate" value="'+json.result[i]['lastpickupdate']+'">';
tableData += '</div>';
}
jQuery("#routePlannerModal #addressList").html(output);
jQuery("#routePlannerModal #addressInformationList").html(tableData);
let draggedItem = null;
document.querySelectorAll('.draggable-item').forEach(item => {
item.addEventListener('dragstart', function(e) {
draggedItem = this;
});
item.addEventListener('dragover', function(e) {
e.preventDefault(); // Necessary for allowing dropping.
});
item.addEventListener('drop', function(e) {
if (this !== draggedItem) {
let list = document.getElementById('addressList');
let currentPos = Array.from(list.children).indexOf(this);
let draggedPos = Array.from(list.children).indexOf(draggedItem);
if (currentPos < draggedPos) {
list.insertBefore(draggedItem, this);
} else {
list.insertBefore(draggedItem, this.nextSibling);
}
}
});
});
}
// Nearby 반경 원 생성 및 추가 함수
var circle;
function addCircle(center) {
circle = new google.maps.Circle({
strokeColor: '#FF0000', // 원의 선 색상
strokeOpacity: 0.8, // 원의 선 불투명도
strokeWeight: 2, // 원의 선 굵기
fillColor: '#FF0000', // 원의 채우기 색상
fillOpacity: 0.05, // 원의 채우기 불투명도
map: map, // 원이 추가될 지도 객체
center: center, // 원의 중심점 (위도, 경도)
radius: 1000 // 원의 반경 (미터 단위) 1km
});
map.fitBounds(circle.getBounds());
}
// Nearby 반경 원 삭제 함수
function removeCircle() {
if (circle) {
circle.setMap(null);
}
}
function drawPoint(){
if(!jQuery(".btn-route-print").hasClass("hidden")){
jQuery(".btn-route-print").addClass("hidden")
}
removeCircle();
clearRoute();
if (currentMarker) {
currentMarker.setMap(null); // Remove existing marker
currentMarker = null;
offLoading();
}
let quantity_array = [];
for(let i=0; i < jQuery(".qty").length; i++) {
quantity_array.push(
{
"checked" : jQuery(".qty:eq("+i+")").is(":checked"),
"min" : jQuery(".qty-min:eq("+i+")").val(),
"max" : jQuery(".qty-max:eq("+i+")").val()
}
);
}
let data = {
"search_date" : jQuery("#orderdate").val(),
"option" : {
"ordered" : jQuery(".chk-ordered").is(":checked")
},
"driver" : jQuery("#driver").val(),
"search" : jQuery("#search-keyword").val(),
"quantity" : quantity_array
}
api('inqPoint', data, rstInqPoint);
/*COOKIE UPDATE*/
let cookieValue = '0:' + jQuery(".qty-max:eq(0)").val();
for(let i=1;i < jQuery(".qty-min").length-1;i++) {
cookieValue += '_';
cookieValue += jQuery(".qty-min:eq("+i+")").val() + ':' + jQuery(".qty-max:eq("+i+")").val();
}
cookieValue += '_' + jQuery(".qty-min:eq("+(jQuery(".qty-min").length-1)+")").val() + ':MAX';
setCookie('qtySetting',cookieValue);
}
var marker = [];
var markerwindow = [];
var overlay = [];
var clusterer = null;
var clusterInfoGlobal = null;
var typeOrder = { "#FF0000": 1, "#800080": 2, "#FF80FF": 3, "#FFFDD5": 4, "#7B7A7A": 5 }; // Request > Scheduled > Normal > Normal(driver add) > Finished
var rstInqPoint = function(json) {
if (clusterInfoGlobal) {
clusterInfoGlobal.close();
clusterInfoGlobal = null;
}
for(let j=0; j < marker.length; j++) {
marker[j].setMap(null);
marker[j] = null;
if(overlay[j] !== undefined) {
overlay[j].setMap(null);
overlay[j] = null;
}
}
if (clusterer) {
clusterer.setMap(null);
clusterer = null;
}
// 닫힘 배지 마커 정리
if (typeof closedBadgeMarker !== 'undefined' && closedBadgeMarker) {
Object.keys(closedBadgeMarker).forEach(function (idx) {
if (closedBadgeMarker[idx]) closedBadgeMarker[idx].setMap(null);
});
closedBadgeMarker = {};
}
if (window.closedNowSet) window.closedNowSet = new Set();
marker = [];
overlay = [];
let pointGeocoder = new google.maps.Geocoder();
let location = '';
for(let i=0; i < json.result.length; i++) {
let property = {
index : i,
store: json.result[i]['name'],
accountno: json.result[i]['accountno'],
paymentcycle: json.result[i]['paymentcycle'],
maincontainer: json.result[i]['maincontainer'],
rate: json.result[i]['rate'],
estqty: json.result[i]['estqty'],
phone: json.result[i]['phone'],
address: json.result[i]['address'],
paymentType: json.result[i]['payment_type'],
date: json.result[i]['order_date'],
ordertype: json.result[i]['ordertype'],
customerid: json.result[i]['id'],
type: json.result[i]['type'],
d_status: json.result[i]['d_status'],
schedule: json.result[i]['schedule'],
colour: "transparency",
color: json.result[i]['color'],
lat: json.result[i]['lat'],
lon: json.result[i]['lon'],
comment: json.result[i]['comment'],
container_location: json.result[i]['container_location'],
lastPickupDate: json.result[i]['last_pickup_date'],
lastPaidDate: json.result[i]['last_paid_date'],
duid: json.result[i]['duid'],
druid: json.result[i]['druid'],
rnote: json.result[i]['rnote'],
containers: json.result[i]['containers'] || [],
<? if ($_SESSION['ss_LEVEL'] == 9) { ?>
orderFlag: json.result[i]['orderFlag']
<? } else { ?>
orderFlag: ''
<? } ?>
};
let icon = (json.result[i]['type'] === 'flag')?getFlagIcon(json.result[i]['color']):getMarkerIcon(json.result[i]['color']);
//let orderFlagValue = (json.result[i]['orderFlag'] === '1')?"":"DISABLED";
if(json.result[i]['lat'].length < 1) {
pointGeocoder.geocode({ address: json.result[i]['address'] }, (results, status) => {
if (status === "OK" && results[0]) {
location = results[0].geometry.location;
latitude = results[0].geometry.location.lat();
longitude = results[0].geometry.location.lng();
let updtPointdata = {
"marker_index" : i,
"id" : json.result[i]['id'],
"accountno" : json.result[i]['accountno'],
"lat" : latitude,
"lon" : longitude,
"property" : property,
"name" : json.result[i]['name'],
"qty" : json.result[i]['estqty']
// 2025.06.19
//"qty" : json.result[i]['qty']
}
api('updtPoint', updtPointdata, rstUpdtPoint);
} else {
console.error("Geocoding failed for the account address: " + status);
}
});
}else{
location = {lat: parseFloat(json.result[i]['lat']), lng: parseFloat(json.result[i]['lon'])};
marker[i] = new google.maps.Marker({
position: location,
map: map,
icon: icon,
animation: (property.rnote && /\S/.test(property.rnote) && property.d_status !== 'F') ? google.maps.Animation.BOUNCE : null
});
marker[i].customType = property.type; //'flag' 또는 'map-marker'
marker[i].property = property;
// Add an info window with company information
markerwindow[i] = new google.maps.InfoWindow({
content: `<strong>`+json.result[i]['name']+`</strong><br>Est. Quantity : `+json.result[i]['estqty']
});
marker[i].addListener('mouseover', () => {
markerwindow[i].open(map, marker[i]);
});
google.maps.event.addListener(markerwindow[i], 'domready', function() {
// InfoWindow의 닫기 버튼만 숨기기
const iwOuter = jQuery(".gm-style-iw").last();
const closeBtn = iwOuter.parent().find(".gm-ui-hover-effect");
closeBtn.css("display","none");
});
marker[i].addListener('mouseout', () => {
markerwindow[i].close();
});
marker[i].addListener('click', function() {
if(overlay[i]) {
overlay[i].setMap(null);
}
let content = buildContent(property); // 클릭 시점 생성 → closedNowSet(닫힘 배지) 반영
overlay[i] = new CustomOverlay(map, marker[i].getPosition(), content.innerHTML, i);
});
}
}
// 클러스터 생성
if (marker.length > 0) {
// flag 타입만 필터링
const flagMarkers = marker.filter(m => m.customType === 'flag');
if (flagMarkers.length > 0) {
// 사이즈 줄이기
const algorithm = new markerClusterer.GridAlgorithm({
gridSize: 5,
maxDistance: 50,
});
clusterer = new markerClusterer.MarkerClusterer({
map,
markers: flagMarkers,
algorithm,
renderer: {
render: ({ count, position, markers }) => {
// 클러스터 안에 rnote 있는 flag 마커가 한 개라도 포함됐는지 확인
const hasRnoteMarker = markers.some(m =>
m.property && m.property.rnote && /\S/.test(m.property.rnote)
);
markers.sort((a, b) => {
const t1 = a.property.color || "#FF80FF";
const t2 = b.property.color || "#FF80FF";
return typeOrder[t1] - typeOrder[t2];
});
const topColor = markers[0]?.property?.color || "#4285f4";
const size = Math.min(50, 30 + Math.log(count) * 10);
const textColor = (String(topColor).toUpperCase() === "#FFFDD5") ? "#000" : "#fff";
// 클러스터 내 닫힘 마커: 남은시간 최대값을 알약으로 표시(느낌표 최소화).
// 시간이 있는 닫힘이 하나도 없고 "오늘 더 안 열림"만 있으면 !
const closedProps = markers.map(m => m.property).filter(p => isClosedNowProp(p));
const closedMins = closedProps
.map(p => closedMinutesFor(p.customerid))
.filter(v => v != null && v > 0);
const clusterMaxLabel = closedMins.length ? remainLabel(Math.max.apply(null, closedMins)) : null;
let W = 34;
let closedBadgeSvg = '';
if (clusterMaxLabel) {
const pw = Math.max(22, 12 + clusterMaxLabel.length * 7);
W = Math.max(34, 21 + pw + 2);
closedBadgeSvg =
`<rect x="21" y="0.5" width="${pw}" height="15" rx="7.5" fill="#f0ad4e" stroke="#ffffff" stroke-width="1"/>
<text x="${21 + pw / 2}" y="12" text-anchor="middle" font-size="11" font-weight="bold" font-family="${BADGE_FONT}" fill="#5a3d00">${clusterMaxLabel}</text>`;
} else if (closedProps.length > 0) {
closedBadgeSvg =
`<circle cx="27" cy="7" r="6.5" fill="#e53935" stroke="#fff" stroke-width="1.5"/>
<text x="27" y="10.5" text-anchor="middle" font-size="9" font-weight="bold" font-family="${BADGE_FONT}" fill="#fff">!</text>`;
}
return new google.maps.Marker({
position,
icon: {
url:
"data:image/svg+xml;charset=UTF-8," +
encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="34">
<circle cx="15" cy="15" r="13"
fill="${topColor}" />
<text x="15" y="20" text-anchor="middle"
font-size="12" font-family="${BADGE_FONT}" fill="${textColor}">${count}</text>
${closedBadgeSvg}
</svg>
`),
scaledSize: new google.maps.Size(W, 34),
anchor: new google.maps.Point(15, 15),
},
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
animation: (
// 클러스터 안에 rnote가 있으면서 finished가 아닌 마커가 1개라도 있는 경우만 Bounce
markers.some(m =>
m.property &&
m.property.rnote && /\S/.test(m.property.rnote) &&
m.property.d_status !== 'F'
)
) ? google.maps.Animation.BOUNCE : null,
});
},
},
onClusterClick: (...args) => {
let clusterObj = null;
if (args.length === 1 && args[0]) {
const a = args[0];
clusterObj = a.cluster || a;
} else if (args.length >= 2) {
clusterObj = args[1];
}
if (!clusterObj) return false;
const markersInCluster =
clusterObj.markers ||
(typeof clusterObj.getMarkers === "function"
? clusterObj.getMarkers()
: []) ||
[];
const clusterCenter =
clusterObj.position ||
(typeof clusterObj.getCenter === "function"
? clusterObj.getCenter()
: null);
if (!markersInCluster.length || !clusterCenter) return false;
if (clusterInfoGlobal) {
clusterInfoGlobal.close();
}
markersInCluster.sort((a, b) => {
const t1 = a.property.color || "#FF80FF";
const t2 = b.property.color || "#FF80FF";
return typeOrder[t1] - typeOrder[t2];
});
let html =
"<div style='min-width:220px;max-height:240px;overflow:auto'>";
markersInCluster.forEach((m) => {
const p = m.property || {};
const hasRnote = p.rnote && /\S/.test(p.rnote);
const isClosed = isClosedNowProp(p);
const openInLbl = isClosed ? remainLabel(closedMinutesFor(p.customerid)) : null;
html += `
<div class="cluster-item" data-marker-index="${p.index}" style="display:flex; align-items:center; margin-bottom:4px; cursor:pointer;">
<span style="width:15px; height:15px; margin-right:6px; background-color:${p.color || "#ccc"}; display:inline-block; border:1px solid #999;"></span>
<strong>${p.store ?? "—"}</strong>
${
isClosed
? (openInLbl
? `<span title="Opens in ${openInLbl}" style="margin-left:6px; display:inline-flex; align-items:center; padding:0 7px; height:16px; border-radius:8px; background:#f0ad4e; color:#5a3d00; font-size:11px; font-weight:bold;">${openInLbl}</span>`
: `<span title="Closed today" style="margin-left:6px; display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; border-radius:8px; background:#e53935; color:#fff; font-size:11px; font-weight:bold;">!</span>`)
: ""
}
${
hasRnote
? `<span style="margin-left:6px; padding:2px 4px; background:#FF9800; color:white; border-radius:3px; font-size:10px;">
notice
</span>`
: ""
}
</div>
<div style="margin-left:22px; font-size:12px; color:#333;">
Est. Quantity : ${p.estqty ?? "-"}
</div>
<hr style="margin:7px 0;">
`;
});
html += "</div>";
const clusterInfo = new google.maps.InfoWindow({
content: html,
position: clusterCenter,
pixelOffset: new google.maps.Size(0, -25),
});
clusterInfoGlobal = clusterInfo;
clusterInfo.open(map);
google.maps.event.addListenerOnce(
clusterInfo,
"domready",
function () {
document.querySelectorAll(".cluster-item").forEach((el) => {
el.addEventListener("click", () => {
const idx = el.getAttribute("data-marker-index");
if (idx !== null && marker[idx]) {
google.maps.event.trigger(marker[idx], "click");
}
clusterInfo.close();
});
});
}
);
return false;
},
});
}
// 클러스터링이 끝났을 때 실행되는 이벤트
clusterer.addListener('clusteringend', () => {
marker.forEach(m => {
if (!m.getMap()) return;
if (m.property?.rnote && /\S/.test(m.property.rnote) && m.property.d_status !== 'F') {
m.setAnimation(google.maps.Animation.BOUNCE);
} else {
m.setAnimation(null);
}
});
// 클러스터로 숨겨진 마커의 닫힘 배지도 같이 숨김/표시
if (typeof syncClosedBadges === 'function') syncClosedBadges();
});
}
applyClosedBadges();
}
function showInfoWindow(position, title, quantity, infoType) {
const showClose = infoType === 'cluster'; // 클러스터일 때만 X 표시
const infoContent = `
<div class="info-window">
${showClose ? '<button class="close" onclick="closeInfoWindow()">×</button>' : ''}
<div class="info-body">
<strong>${title}</strong><br>
Est. Quantity: ${quantity}
</div>
</div>
`;
infoWindow.setContent(infoContent);
infoWindow.setPosition(position);
infoWindow.open(map);
}
function addDragEvent(customOverlay) {
let div = customOverlay.div;
//for mobile
let table = div.querySelector('table');;
let title = div.querySelector('h4');
let isDragging = false;
let startX, startY;
let clickPrevent = false;
let startDrag = (e) => {
isDragging = true;
clickPrevent = false;
startX = (e.clientX || e.touches[0].clientX) - div.offsetLeft;
startY = (e.clientY || e.touches[0].clientY) - div.offsetTop;
e.preventDefault();
// Prevent map from dragging
map.setOptions({ draggable: false });
};
let onDrag = (e) => {
if (isDragging) {
const currentX = e.clientX || e.touches[0].clientX;
const currentY = e.clientY || e.touches[0].clientY;
const dx = currentX - startX;
const dy = currentY - startY;
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
clickPrevent = true;
}
div.style.left = dx + 'px';
div.style.top = dy + 'px';
e.preventDefault();
}
};
let endDrag = (e) => {
if (isDragging) {
isDragging = false;
e.preventDefault();
// Re-enable map dragging
map.setOptions({ draggable: true });
if (!clickPrevent) {
// If not dragging, it is a click event
const target = e.target || e.touches[0].target;
if (target.tagName === 'BUTTON') {
target.click();
}
}
// Update the overlay position to the new location
const overlayProjection = customOverlay.getProjection();
const divPosition = new google.maps.Point(parseInt(div.style.left, 10), parseInt(div.style.top, 10));
const newPosition = overlayProjection.fromDivPixelToLatLng(divPosition);
customOverlay.position = newPosition;
customOverlay.draw();
}
};
div.addEventListener('mousedown', startDrag);
table.addEventListener('touchstart', startDrag, { passive: false });
title.addEventListener('touchstart', startDrag, { passive: false });
document.addEventListener('mousemove', onDrag);
document.addEventListener('touchmove', onDrag, { passive: false });
document.addEventListener('mouseup', endDrag);
document.addEventListener('touchend', endDrag, { passive: false });
}
// 깃발 모양 마커 아이콘 생성 함수에 16진수 색상 코드 사용
function getFlagIcon(color) {
return {
path: 'M64 32C64 14.3 49.7 0 32 0S0 14.3 0 32V64 368 480c0 17.7 14.3 32 32 32s32-14.3 32-32V352l64.3-16.1c41.1-10.3 84.6-5.5 122.5 13.4c44.2 22.1 95.5 24.8 141.7 7.4l34.7-13c12.5-4.7 20.8-16.6 20.8-30V66.1c0-23-24.2-38-44.8-27.7l-9.6 4.8c-46.3 23.2-100.8 23.2-147.1 0c-35.1-17.6-75.4-22-113.5-12.5L64 48V32z', // 깃발 모양의 간단한 SVG 경로
fillColor: color, // 16진수 색상 코드
fillOpacity: 1,
strokeColor: 'black',
strokeWeight: 0.5,
scale: 0.04,
};
}
// 원 모양 마커 아이콘 생성 함수에 16진수 색상 코드 사용
function getMarkerIcon(color) {
return {
path: 'M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z',
fillColor: color,
fillOpacity: 1,
strokeColor: '#000',
strokeWeight: 0.5,
scale: 0.04,
};
}
// 현재위치 마커 아이콘 생성 함수에 16진수 색상 코드 사용
function getCurrentIcon(color) {
return {
path: 'M256 0c17.7 0 32 14.3 32 32V66.7C368.4 80.1 431.9 143.6 445.3 224H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H445.3C431.9 368.4 368.4 431.9 288 445.3V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V445.3C143.6 431.9 80.1 368.4 66.7 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H66.7C80.1 143.6 143.6 80.1 224 66.7V32c0-17.7 14.3-32 32-32zM128 256a128 128 0 1 0 256 0 128 128 0 1 0 -256 0zm128-80a80 80 0 1 1 0 160 80 80 0 1 1 0-160z',
fillColor: color,
fillOpacity: 1,
strokeColor: '#000',
strokeWeight: 0.5,
scale: 0.04,
};
}
function CustomOverlay(map, position, content, index) {
this.position = position;
this.content = content;
this.index = index;
this.map = map;
this.div = null;
this.setMap(map);
}
var rstUpdtPoint = function(json) {
let location = {lat: parseFloat(json.lat), lng: parseFloat(json.lon)};
let icon = (json.property['type'] === 'flag')?getFlagIcon(json.property['color']):getMarkerIcon(json.property['color']);
marker[parseInt(json.markerIndex)] = new google.maps.Marker({
position: location,
map: map,
icon: icon
});
// Add an info window with company information
markerwindow[parseInt(json.markerIndex)] = new google.maps.InfoWindow({
content: `<strong>`+json.name+`</strong><br>Est. Quantity : `+json.estqty
});
marker[parseInt(json.markerIndex)].addListener('mouseover', () => {
markerwindow[parseInt(json.markerIndex)].open(map, marker[parseInt(json.markerIndex)]);
});
marker[parseInt(json.markerIndex)].addListener('mouseout', () => {
markerwindow[parseInt(json.markerIndex)].close();
});
marker[parseInt(json.markerIndex)].addListener('click', function() {
let content = buildContent(json.property); // 클릭 시점 생성 → closedNowSet 반영
overlay[parseInt(json.markerIndex)] = new CustomOverlay(map, marker[parseInt(json.markerIndex)].getPosition(), content.innerHTML, parseInt(json.markerIndex));
});
}
async function initMap() {
// Request needed libraries.
const { Map } = await google.maps.importLibrary("maps");
map = new Map(document.getElementById("map"), {
zoom: 11,
center,
mapId: "4504f8b37365c3d0",
gestureHandling: 'greedy',
disableDefaultUI: true,
clickableIcons: false
});
/*
//find current position
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
center = { lat: position.coords.latitude, lng: position.coords.longitude };
currentLocation = { lat: position.coords.latitude, lon: position.coords.longitude };
map.setCenter(center);
}
);
}
*/
jQuery("#driver").trigger("change");
drawPoint();
CustomOverlay.prototype = new google.maps.OverlayView();
CustomOverlay.prototype.onAdd = function() {
var div = document.createElement('div');
div.style.position = 'absolute';
div.innerHTML = this.content;
this.div = div;
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(div);
// 닫기 버튼에 이벤트 리스너 추가
var closeButton = div.getElementsByClassName('overlay-close-'+this.index)[0];
closeButton.addEventListener('click', () => {
this.hide();
});
jQuery(".btn-add-order:eq("+this.index+")").click(function(){
addOrder(this.id);
});
addDragEvent(this);
};
CustomOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
var position = overlayProjection.fromLatLngToDivPixel(this.position);
var div = this.div;
div.style.left = position.x + 'px';
div.style.top = position.y + 'px';
};
CustomOverlay.prototype.onRemove = function() {
if (this.div) {
this.div.parentNode.removeChild(this.div);
this.div = null;
}
};
CustomOverlay.prototype.hide = function() {
if (this.div) {
this.div.style.visibility = 'hidden';
}
};
CustomOverlay.prototype.show = function() {
if (this.div) {
this.div.style.visibility = 'visible';
}
};
}
function toggleHighlight(markerView, property) {
if (!markerView.content.classList.contains("highlight")) {
markerView.content.classList.add("highlight");
markerView.zIndex = 1;
}else{
}
/*
//info modal init
jQuery("#customerShortInfo .c_uid").val(property['customerid']);
jQuery(".shortinfo-payment-type").text(property['paymentType']);
jQuery(".shortinfo-comment").val(property['comment']);
jQuery(".shortinfo-container-location").val(property['container_location']);
//history modal init
}*/
}
function showShortInfo(id){
let loadingTxt = 'Loading...';
jQuery("#customerShortInfo .shortinfo-payment-type").text(loadingTxt);
jQuery("#customerShortInfo #c_comment_ri,#customerShortInfo #c_location").val(loadingTxt).attr("disabled","disabled");
jQuery("#customerShortInfo .btn-sub").attr("disabled","disabled");
// Access Time / Closing Days 표시 조각 로드
jQuery("#customerShortInfo .c_uid").val(id);
if (typeof loadCustomerAccess === 'function') loadCustomerAccess(id);
let data = {
"id" : id
}
api('inqShortInfo', data, rstInqShortInfo);
}
var rstInqShortInfo = function(json) {
jQuery("#customerShortInfo .c_uid").val(json.result['id']);
jQuery("#customerShortInfo .c_accountno").val(json.result['accountno']);
jQuery("#customerShortInfo .shortinfo-store-name").text(json.result['name']);
jQuery("#customerShortInfo .shortinfo-payment-type").text(json.result['payment_type']);
jQuery("#customerShortInfo .shortinfo-payment-string").text(json.result['paymentstring']);
jQuery("#customerShortInfo #c_comment_ri").val(json.result['comment']).attr("disabled",false);
jQuery("#customerShortInfo #c_location").val(json.result['location']).attr("disabled",false);
jQuery("#customerShortInfo .btn-sub").attr("disabled",false);
if (json.result.install_img && json.result.install_img !== "") {
jQuery("#installImgArea").html(
'<a href="'+ json.result.install_img +'" target="_blank">View Image</a>'
);
} else {
jQuery("#installImgArea").html('<span>No image</span>');
}
}
function updtShortInfo(){
let data = {
"id" : jQuery("#customerShortInfo .c_uid").val(),
"accountno" : jQuery("#customerShortInfo .c_accountno").val(),
"comment" : jQuery(".shortinfo-comment").val(),
"location" : jQuery(".shortinfo-container-location").val()
}
api('updtShortInfo', data, rstUpdtShortInfo);
}
var rstUpdtShortInfo = function(json) {
drawPoint();
}
function showShortNotice(id){
let loadingTxt = 'Loading...';
jQuery("#map-modal-note #r_note").val('');
jQuery("#map-modal-note #r_note").text(loadingTxt);
let data = {
"id" : id
}
api('inqShortNotice', data, rstInqShortNotice);
}
var rstInqShortNotice = function(json) {
jQuery("#map-modal-note #r_note").val(json.result['rnote']).attr("disabled",false);
}
window.SimpleInstall = {
customer_uid: null,
driver_uid: null,
job_type: null
};
document.addEventListener('click', function(e){
const btn = e.target.closest('.btn-container-request');
if (!btn) return;
const payload = {
c_uid: btn.dataset.cuid,
c_name: btn.dataset.cname,
driver_uid: btn.dataset.driver,
member_uid: btn.dataset.member
};
api('install_wait_get_active', { c_uid: payload.c_uid }, function(res){
if (!res || !res.ok) return;
if (res.exists) openSimpleInstallModal(payload, res.data);
else openSimpleInstallModal(payload, null);
});
});
document.addEventListener('click', function(e){
const btn = e.target.closest('.btn-image-modal');
if (!btn) return;
openCustomerImages(
btn.dataset.cuid,
btn.dataset.cname
);
});
function openSimpleInstallModal(data, existing = null) {
// 기본 세팅
SimpleInstall.customer_uid = data.c_uid;
SimpleInstall.driver_uid = data.driver_uid;
SimpleInstall.member_uid = data.member_uid;
document.getElementById('simpleInstallCustomerName').innerText = data.c_name;
document.getElementById('simpleInstallCustomerUid').value = data.c_uid;
document.getElementById('simpleInstallDriverUid').value = data.driver_uid;
const deleteBtn = document.getElementById('btnSimpleInstallDelete');
deleteBtn.style.display = 'none';
// 초기화
document.getElementById('simpleInstallPhotos').value = '';
document.getElementById('simpleInstallNote').value = '';
document.querySelectorAll('.simple-job-btn').forEach(b => b.classList.remove('active'));
// 메타 영역
const metaWrap = document.getElementById('simpleInstallMeta');
metaWrap.style.display = 'none';
document.getElementById('siStatus').innerText = '';
document.getElementById('siAddedBy').innerText = '';
document.getElementById('siAddedAt').innerText = '';
document.getElementById('siScheduled').innerText = '';
// 기본: 새로 생성 모드
SimpleInstall.iw_uid = null;
SimpleInstall.job_type = null;
// 기본: 입력 가능
setSimpleInstallEditable(true);
if (!existing) {
document.getElementById('btnSimpleInstallSubmit').disabled = true;
renderSimpleInstallPhotos([]);
return;
}
// 기존 요청 있음
SimpleInstall.iw_uid = existing.iw_uid;
SimpleInstall.job_type = existing.iw_work_type || null;
// 메타 표시
metaWrap.style.display = 'block';
document.getElementById('siStatus').innerText = existing.iw_status || '';
document.getElementById('siAddedBy').innerText = existing.iw_request_by_name || '';
document.getElementById('siAddedAt').innerText = (existing.iw_created_at || existing.iw_request_at || '');
const sched = existing.di_install_date ? (existing.di_install_date + (existing.di_install_time ? (' ' + existing.di_install_time) : '')) : '-';
document.getElementById('siScheduled').innerText = sched;
document.getElementById('simpleInstallNote').value = existing.iw_request_note || '';
// job 버튼 active
if (existing.iw_work_type) {
document.querySelectorAll('.simple-job-btn').forEach(btn=>{
btn.classList.toggle('active', btn.dataset.job === existing.iw_work_type);
});
}
// 상태별 권한
if (existing.iw_status === 'DRAFT') {
deleteBtn.style.display = 'inline-block';
setSimpleInstallEditable(true);
document.getElementById('btnSimpleInstallSubmit').disabled = false;
} else if (existing.iw_status === 'WAITING' || existing.iw_status === 'ASSIGNED') {
deleteBtn.style.display = 'none';
setSimpleInstallEditable(false);
document.getElementById('btnSimpleInstallSubmit').disabled = true;
} else {
// 안전장치
deleteBtn.style.display = 'none';
setSimpleInstallEditable(false);
document.getElementById('btnSimpleInstallSubmit').disabled = true;
}
// 사진 표시
if (existing && existing.photos) {
renderSimpleInstallPhotos(existing.photos);
} else {
renderSimpleInstallPhotos([]);
}
}
function setSimpleInstallEditable(canEdit) {
// note
document.getElementById('simpleInstallNote').disabled = !canEdit;
// photos
document.getElementById('simpleInstallPhotos').disabled = !canEdit;
// job buttons
document.querySelectorAll('.simple-job-btn').forEach(btn=>{
btn.disabled = !canEdit;
});
// Submit 버튼 텍스트는?
// document.getElementById('btnSimpleInstallSubmit').innerText = canEdit ? 'Save' : 'Locked';
}
function renderSimpleInstallPhotos(photos){
const wrap = document.getElementById('simpleInstallPhotoList');
const outer = document.getElementById('simpleInstallPhotoWrap');
if (!wrap || !outer) return;
wrap.innerHTML = '';
if (!photos || photos.length === 0) {
outer.style.display = 'none';
return;
}
outer.style.display = 'block';
photos.forEach(photo => {
if (!photo.i_filename) return;
const basePath = (photo.i_filepath || '').trim();
const filenames = photo.i_filename
.split(',')
.map(f => f.trim())
.filter(Boolean);
filenames.forEach(name => {
const fullUrl = basePath.endsWith('/')
? basePath + name
: basePath + '/' + name;
const div = document.createElement('div');
div.style.width = '120px';
div.innerHTML = `
<img src="${fullUrl}"
style="width:100%; height:100px; object-fit:cover; border-radius:8px; border:1px solid #ddd;">
`;
wrap.appendChild(div);
});
});
}
function escapeHtml(str) {
if (str == null) return '';
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
document.querySelectorAll('.simple-job-btn').forEach(btn => {
btn.onclick = function() {
document.querySelectorAll('.simple-job-btn')
.forEach(b => b.classList.remove('active'));
this.classList.add('active');
SimpleInstall.job_type = this.dataset.job;
document.getElementById('btnSimpleInstallSubmit').disabled = false;
};
});
document.getElementById('btnSimpleInstallSubmit').onclick = function(){
if (!SimpleInstall.job_type) return;
this.disabled = true;
$installNote = document.getElementById('simpleInstallNote').value;
const payload = {
c_uid: SimpleInstall.customer_uid,
driver_uid: SimpleInstall.driver_uid,
member_uid: SimpleInstall.member_uid,
job_type: SimpleInstall.job_type,
request_note: $installNote
};
// 기존 draft면 update, 아니면 create
const action = SimpleInstall.iw_uid ? 'install_wait_update_draft' : 'install_wait_create_draft';
if (SimpleInstall.iw_uid) {
payload.iw_uid = SimpleInstall.iw_uid;
}
api(action, payload, function(res){
if (!res || !res.ok){
alert('Failed to save request');
document.getElementById('btnSimpleInstallSubmit').disabled = false;
return;
}
const iw_uid = SimpleInstall.iw_uid || res.iw_uid;
// 사진 처리
const files = document.getElementById('simpleInstallPhotos').files;
if (!files || files.length === 0){
finishSimpleInstall();
return;
}
const fd = new FormData();
fd.append('actionStr', 'ADDIMAGE');
fd.append('i_type', 'install_order');
fd.append('i_customeruid', SimpleInstall.customer_uid);
fd.append('i_memberuid', SimpleInstall.member_uid);
fd.append('i_createdby', <?=$_SESSION['ss_UID']?>);
fd.append('i_note', $installNote);
fd.append('i_sourceuid', iw_uid);
// 파일명 순서대로
[...files].sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }))
.forEach(file => {
fd.append('upload_file[]', file);
});
fetch('/lib/user_process.php', {
method: 'POST',
body: fd
})
.then(() => finishSimpleInstall())
.catch(() => finishSimpleInstall());
});
};
document.getElementById('btnSimpleInstallDelete').onclick = function(){
if (!SimpleInstall.iw_uid) return;
if (!confirm('Are you sure you want to delete this draft?')) return;
this.disabled = true;
api('install_wait_delete_draft', {
iw_uid: SimpleInstall.iw_uid
}, function(res){
if (!res || !res.ok){
alert('Failed to delete draft');
document.getElementById('btnSimpleInstallDelete').disabled = false;
return;
}
$('#map-modal-container-request').modal('hide');
});
};
function finishSimpleInstall(){
$('#map-modal-container-request').modal('hide');
setTimeout(()=>{
location.reload(); // Map 리로드
}, 300);
}
function showPopupMessage_container(message, type = 'success', duration = 1200) {
const body = document.querySelector('#myModalPopup .myModalPopup-body');
if (!body) return;
body.innerHTML = message;
if (type === 'success') {
body.style.backgroundColor = '#2A9B56'; // green
} else {
body.style.backgroundColor = '#FF8205'; // orange
}
$('#myModalPopup').modal('show');
setTimeout(function () {
$('#myModalPopup').modal('hide');
}, duration);
}
function inqCurrent() {
onLoading();
removeCircle();
//set to init
jQuery(".chk-ordered").prop("checked",true);
jQuery(".chk-scheduled").prop("checked",false);
jQuery(".chk-requested").prop("checked",false);
jQuery("#search-keyword").val("");
jQuery(".qty").prop("checked", true);
if (currentMarker) {
currentMarker.setMap(null); // Remove existing marker
currentMarker = null;
// map-marker만 제거
marker.forEach(marker => {
if (marker.customType === 'map-marker') {
marker.setMap(null);
}
});
offLoading();
}else{
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
const currentPosition = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
if (currentMarker) {
currentMarker.setMap(null); // Remove existing marker
}
currentMarker = new google.maps.Marker({
position: currentPosition,
map: map,
icon: getCurrentIcon('#dc3545'),
title: "Current Location"
});
map.setCenter(currentPosition);
addCircle(currentPosition);
let quantity_array = [];
for(let i=0; i < jQuery(".qty").length; i++) {
quantity_array.push(
{
"checked" : jQuery(".qty:eq("+i+")").is(":checked"),
"min" : jQuery(".qty-min:eq("+i+")").val(),
"max" : jQuery(".qty-max:eq("+i+")").val()
}
);
}
let data = {
"center_lat" : currentPosition.lat,
"center_lon" : currentPosition.lng,
"search_date" : jQuery("#orderdate").val(),
"driver" : jQuery("#driver").val(),
"quantity" : quantity_array
}
api('inqNearby', data, rstInqPoint);
smoothZoom(map, 14, map.getZoom());
offLoading();
}, function() {
offLoading();
alert("Unable to retrieve your location.");
});
} else {
offLoading();
alert("Geolocation is not supported by this browser.");
}
}
}
$('#customerImageModal').on('shown.bs.modal', function () {
const body = document.getElementById('customerImageModalBody');
initCustomerImageSlider(body);
});
function openCustomerImages(customerUid, customerName) {
// const modal = new bootstrap.Modal(
// document.getElementById('customerImageModal')
// );
const nameEl = document.getElementById('imageCustomerName');
if (nameEl) {
nameEl.innerText = customerName || '';
}
const body = document.getElementById('customerImageModalBody');
body.innerHTML = '<div class="text-center text-muted py-5">Loading images...</div>';
//modal.show();
const params = new URLSearchParams({
customer_uid: customerUid,
i_type: 'ALL'
});
fetch('/lib/customer_image.php?' + params.toString())
.then(res => res.text())
.then(html => {
body.innerHTML = html;
initCustomerImageSlider(body);
})
.catch(err => {
console.error(err);
body.innerHTML = '<div class="text-danger text-center py-5">Failed to load images.</div>';
});
}
function inqNearby(coord){
removeCircle();
//set to init
jQuery(".chk-ordered").prop("checked",true);
jQuery(".chk-scheduled").prop("checked",false);
jQuery(".chk-requested").prop("checked",false);
jQuery("#search-keyword").val("");
jQuery(".qty").prop("checked",true);
if (currentMarker) {
currentMarker.setMap(null); // Remove existing marker
}
let coordArr = coord.split(":");
center = { lat: parseFloat(coordArr[0]), lng: parseFloat(coordArr[1]) };
map.setCenter(center);
addCircle(center);
let quantity_array = [];
for(let i=0; i < jQuery(".qty").length; i++) {
quantity_array.push(
{
"checked" : jQuery(".qty:eq("+i+")").is(":checked"),
"min" : jQuery(".qty-min:eq("+i+")").val(),
"max" : jQuery(".qty-max:eq("+i+")").val()
}
);
}
let data = {
"center_lat" : coordArr[0],
"center_lon" : coordArr[1],
"search_date" : jQuery("#orderdate").val(),
"driver" : jQuery("#driver").val(),
"quantity" : quantity_array
}
api('inqNearby', data, rstInqPoint);
}
function initHistory(id){
jQuery("#map-modal-history tbody").html("<tr><td colspan='5' class='text-center'>Loading</td></tr>");
let data = {
"id" : id
}
api('inqHistory', data, rstInqHistory);
}
var rstInqHistory = function(json) {
let output = '';
let j = json.result.length;
for(let i=0; i < json.result.length; i++) {
output += '<tr>';
output += '<td>' + j + '</td>';
output += '<td>' + json.result[i]['date'] + '</td>';
output += '<td>' + json.result[i]['quantity'] + '</td>';
output += '<td>' + json.result[i]['driver'] + '</td>';
output += '<td>' + json.result[i]['paid'] + '</td>';
output += '</tr>';
j -= 1;
}
jQuery("#map-modal-history tbody").html(output);
}
// 지금 닫힘(closed now) 고객 집합 + 상세(다음 오픈까지 남은 분 등)
window.closedNowSet = window.closedNowSet || new Set();
window.closedNowInfo = window.closedNowInfo || {}; // { customerid: {open, next_open, minutes_until_open, ...} }
// 고객의 "다음 오픈까지 남은 분" (없으면 null)
function closedMinutesFor(cid) {
var d = window.closedNowInfo && window.closedNowInfo[String(cid)];
return (d && typeof d.minutes_until_open === 'number') ? d.minutes_until_open : null;
}
// 남은 분 → 짧은 라벨. 오늘 더 안 열면(null/0 이하) null → '!' 로 표시됨
function remainLabel(mins) {
if (mins == null || mins <= 0) return null;
if (mins < 60) return mins + 'm';
var h = Math.floor(mins / 60), m = mins % 60;
return m ? (h + 'h ' + m + 'm') : (h + 'h');
}
// 뱃지 텍스트 폰트 (HTML 배지와 통일). 실제 UI 폰트에 맞게 바꿔도 됨.
var BADGE_FONT = "Arial, Helvetica, sans-serif";
// 남은시간 앰버 알약 배지 SVG
function timeBadgeSvg(label) {
var w = Math.max(22, 12 + label.length * 7);
return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="18">' +
'<rect x="0.5" y="0.5" width="' + (w - 1) + '" height="17" rx="8.5" fill="#f0ad4e" stroke="#ffffff" stroke-width="1"/>' +
'<text x="' + (w / 2) + '" y="13" text-anchor="middle" font-size="12" font-weight="bold" font-family="' + BADGE_FONT + '" fill="#5a3d00">' + label + '</text>' +
'</svg>');
}
// 닫힘 배지(빨간 원 + 흰 !) 아이콘
var CLOSED_BADGE_SVG = 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">' +
'<circle cx="10" cy="10" r="8.5" fill="#e53935" stroke="#ffffff" stroke-width="2"/>' +
'<text x="10" y="14.5" text-anchor="middle" font-size="13" font-weight="bold" font-family="' + BADGE_FONT + '" fill="#ffffff">!</text>' +
'</svg>');
// 깃발 대비 배지 위치.
// 깃발 심볼은 anchor (0,0) → latlng 이 깃발 좌상단, 깃발은 우/하로 그려짐(약 17x20px).
// 배지 아이콘 20x20 기준, 배지 중심 화면오프셋 = (10 - x, 10 - y).
// 아래 값 = 배지 중심이 latlng 기준 (17, -1) → 깃발 배너 우상단.
// 깃발과 안 맞으면 x/y 두 숫자만 조정.
var BADGE_ANCHOR = { x: -7, y: 11 };
var closedBadgeMarker = {}; // markerArrayIndex -> 배지 google.maps.Marker
function makeClosedBadge(pos, label) {
var icon;
if (label) {
var w = Math.max(22, 12 + label.length * 7);
icon = {
url: timeBadgeSvg(label),
scaledSize: new google.maps.Size(w, 18),
anchor: new google.maps.Point(-6, 15), // 깃발 우상단, 알약 좌측 기준
};
} else {
icon = {
url: CLOSED_BADGE_SVG,
scaledSize: new google.maps.Size(20, 20),
anchor: new google.maps.Point(BADGE_ANCHOR.x, BADGE_ANCHOR.y),
};
}
return new google.maps.Marker({
position: pos,
map: map,
clickable: false,
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + 1,
icon: icon,
});
}
// 클러스터로 숨겨진(부모 마커가 map 에서 빠진) 배지는 같이 숨김
function syncClosedBadges() {
Object.keys(closedBadgeMarker).forEach(function (idx) {
var b = closedBadgeMarker[idx];
var m = marker[idx];
if (b && m) b.setMap(m.getMap() ? map : null);
});
}
// "지금 닫힘" 표시 대상인지 (Finished = d_status 'F' 는 제외)
function isClosedNowProp(p) {
return !!(p && p.d_status !== 'F' &&
window.closedNowSet && window.closedNowSet.has(String(p.customerid)));
}
// 마커들의 고객 id 로 "지금 닫힘" 조회 → 개별 배지 + 클러스터 재렌더
function applyClosedBadges() {
var ids = [];
marker.forEach(function (m) {
if (m && m.property && m.property.customerid != null) ids.push(String(m.property.customerid));
});
ids = Array.from(new Set(ids));
if (ids.length === 0) return;
jQuery.ajax({
url: '/lib/customer_open_status.php',
method: 'POST',
dataType: 'json',
data: { ids: ids.join(',') },
success: function (res) {
window.closedNowSet = new Set(((res && res.closed) ? res.closed : []).map(String));
window.closedNowInfo = (res && res.detail) ? res.detail : {};
// 기존 배지 제거
Object.keys(closedBadgeMarker).forEach(function (idx) {
if (closedBadgeMarker[idx]) closedBadgeMarker[idx].setMap(null);
});
closedBadgeMarker = {};
// 닫힌 마커마다 배지 생성 (오늘 이따 열면 남은시간, 아니면 !)
marker.forEach(function (m, idx) {
if (!m || !m.property) return;
if (isClosedNowProp(m.property)) {
var lbl = remainLabel(closedMinutesFor(m.property.customerid));
closedBadgeMarker[idx] = makeClosedBadge(m.getPosition(), lbl);
m.setTitle(lbl ? ('Opens in ' + lbl) : 'Closed today');
}
});
syncClosedBadges();
// 클러스터 아이콘도 ! 반영되도록 재렌더
if (typeof clusterer !== 'undefined' && clusterer && typeof clusterer.render === 'function') {
clusterer.render();
}
}
});
}
function buildContent(property) {
let content = document.createElement("div");
const chipsHtml = renderContainerChips(property.containers);
const closedBadge = isClosedNowProp(property)
? '<span class="closed-badge">!</span>' : '';
content.classList.add("property");
let innerHtml = `
<div class="point-overlay">
<div class="modal-header">
<h4 class="modal-title">${property.store} ${property.ordertype}</h4>
<!--h4 class="modal-title" onClick="javascript:jQuery('.overlay-close-${property.index}').trigger('click');">${property.store}</h4-->
<div class="close overlay-close-${property.index}">&times;</div>
</div><!--modal-header-->
<div class="store-info">
<table class="tb-info-box">
<!--table class="tb-info-box" onClick="javascript:jQuery('.overlay-close-${property.index}').trigger('click');"-->
<tr>
<td class="td-title-info">Account</td>
<td class="td-text-info">
<span>${property.accountno}</span>
</td>
<td class="td-title-info">Pay & Rate</td>
<td class="td-text-info">
<span style="border: 1px solid #000000; padding:2px; margin-right:8px;">${property.paymentType}</span><span>${property.rate}</span>
</td>
</tr>
<tr>
<td class="td-title-info">Phone</td>
<td class="td-text-info">
<span>${property.phone}</span>
</td>
<td class="td-title-info">Forecast</td>
<td class="td-text-info">
<span>${property.estqty}</span>
</td>
</tr>
<tr>
<td class="td-title-info">Last Pickup</td>
<td class="td-text-info">
<span>${property.lastPickupDate}</span>
</td>
<td class="td-title-info">Last Paid</td>
<td class="td-text-info">
<span>${property.lastPaidDate}</span>
</td>
</tr>
</table>
<div class="address-row" style="margin-bottom:0px;">
<div class="address-title">Address</div>
<div class="address-content">
<a href="https://www.google.com/maps/search/?api=1&query=${property.lat},${property.lon}" target="_blank"
class="address-link"
style="letter-spacing: 0.5px;"
target="_blank"
rel="noopener noreferrer">
<b>${property.address}</b>
</a>
</div>
</div>
<div class="address-row">
<div class="address-title">Container</div>
<div class="address-content">
${chipsHtml}
</div>
</div>`;
innerHtml += `
<div class="text-center map-grid-layout-col-4">
<!-- Row 1 -->
<button type="button"
class="mapBtn btn-info-modal"
data-toggle="modal"
data-target="#map-modal-info"
onClick="showShortInfo(${property.customerid});">
<i class="fa-solid fa-question mapBtn-ico"></i>
<div class="mapBtn-txt">Info ${closedBadge}</div>
</button>
<button type="button"
class="mapBtn btn-grey-modal"
data-toggle="modal"
data-target="#map-modal-history"
onClick="initHistory(${property.customerid});">
<i class="bi bi-folder2-open mapBtn-ico"></i>
<div class="mapBtn-txt">History</div>
</button>
<button type="button"
class="mapBtn btn-orange-modal ${(property.rnote && /\S/.test(property.rnote)) ? 'blink' : ''}"
data-toggle="modal"
data-target="#map-modal-note"
onClick="showShortNotice(${property.duid});">
<i class="fa fa-thumb-tack mapBtn-ico"></i>
<div class="mapBtn-txt">Notice</div>
</button>`;
if(`${property.date}` === 'null') {
innerHtml += `
<button type="button"
class="mapBtn btn-primary-modal addOrderBtn_${property.index}"
onClick="initAddOrder(${property.customerid},${property.index});">
<i class="fa-solid fa-plus mapBtn-ico"></i>
<div class="mapBtn-txt">Add</div>
</button>
`;
} else {
innerHtml += `
<button type="button"
${property.orderFlag}
class="mapBtn btn-red-modal removeOrderBtn_${property.index}"
data-toggle="modal"
data-target="#modal-confirm"
onClick="removeOrder(${property.customerid},${property.duid},${property.index},'${property.accountno}');">
<i class="fa-solid fa-xmark mapBtn-ico"></i>
<div class="mapBtn-txt">Remove</div>
</button>
`;
}
innerHtml += `
<!-- Row 2 -->
<button type="button"
class="mapBtn btn-nearby"
onClick="inqNearby('${property.lat}:${property.lon}');">
<i class="fa-solid fa-diamond-turn-right mapBtn-ico"></i>
<div class="mapBtn-txt">Nearby</div>
</button>
<button type="button"
class="mapBtn btn-container-request"
data-toggle="modal"
data-target="#map-modal-container-request"
data-cuid="${property.customerid}"
data-cname="${property.store}"
data-member="${SS_UID}"
data-driver="${SS_DRUID}">
<i class="bi bi-database-fill mapBtn-ico"></i>
<div class="mapBtn-txt">Container</div>
</button>
<button type="button"
class="mapBtn btn-image-modal"
data-toggle="modal"
data-target="#customerImageModal"
data-cuid="${property.customerid}"
data-cname="${property.store}">
<i class="bi bi-image mapBtn-ico"></i>
<div class="mapBtn-txt">Image</div>
</button>
<button type="button"
class="mapBtn btn-purple-modal"
data-toggle="modal"
data-target="#map-modal-input"
onClick="initInput(${property.customerid},'${property.accountno}');">
<i class="bi bi-truck mapBtn-ico"></i>
<div class="mapBtn-txt">Input</div>
</button>
`;
innerHtml += `</div>`;
content.innerHTML = innerHtml;
return content;
}
var rstInqMapCenter = function(json) {
center = { lat: parseFloat(json.geo['lat']), lng: parseFloat(json.geo['lon']) };
currentLocation = { lat: parseFloat(json.geo['lat']), lon: parseFloat(json.geo['lon']) };
map.setCenter(center);
}
function onLoading(){
jQuery(".loading-overlay").css("display","flex");
}
function offLoading(){
jQuery(".loading-overlay").css("display","none");
}
function imagepopup(c_uid) {
const url = `/lib/customer_image_upload_popup.php?c_uid=${c_uid}`;
window.open(url, "uploadImage", "width=600,height=750,scrollbars=yes");
}
function renderContainerChips(containers) {
if (!Array.isArray(containers) || containers.length === 0) return '';
const grouped = new Map();
for (const cc of containers) {
const key = [
cc.cc_type,
cc.cc_owner_type,
cc.cc_has_lock,
cc.cc_has_wheel,
cc.cc_has_woodframe
].join('|');
if (!grouped.has(key)) grouped.set(key, { cc, cnt: 0 });
grouped.get(key).cnt++;
}
let html = `<div class="container-chips inline-chips">`;
for (const { cc, cnt } of grouped.values()) {
const type = String(cc.cc_type || '').toUpperCase();
let icon = 'bi-box2';
if (type.includes('D')) icon = 'bi-database-fill';
else if (type.includes('B') && !type.includes('BUCKET')) icon = 'bi-archive-fill';
const isRestaurantOwned = (cc.cc_owner_type === 'O');
const chipClass = isRestaurantOwned ? 'chip chip-restaurant' : 'chip chip-company';
let optIcons = '';
if (cc.cc_has_lock === 'Y') optIcons += ' <i class="bi bi-lock-fill"></i>';
if (cc.cc_has_wheel === 'Y') optIcons += ' <i class="bi bi-gear-fill"></i>';
if (cc.cc_has_woodframe === 'Y') optIcons += ' <i class="bi bi-bounding-box-circles"></i>';
html += `
<span class="${chipClass}" title="${isRestaurantOwned ? 'Restaurant-owned (Do not remove)' : 'Customer container'}">
<i class="bi ${icon}"></i>
${cc.cc_type || ''}
${optIcons}
${isRestaurantOwned ? '<span class="chip-flag">O</span>' : ''}
${cnt > 1 ? `<span class="chip-count">x${cnt}</span>` : ''}
</span>
`;
}
html += `</div>`;
return html;
}
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.number-input').forEach(container => {
const input = container.querySelector('.d_sludge');
const leftBtn = container.querySelector('.left');
const rightBtn = container.querySelector('.right');
const step = 5;
leftBtn.addEventListener('click', () => {
const min = input.min ? parseInt(input.min) : -Infinity;
let value = parseInt(input.value || 0);
if (value - step >= min) input.value = value - step;
});
rightBtn.addEventListener('click', () => {
const max = input.max ? parseInt(input.max) : Infinity;
let value = parseInt(input.value || 0);
if (value + step <= max) input.value = value + step;
});
});
});
async function forceRefresh() {
if ('caches' in window) {
const keys = await caches.keys();
await Promise.all(keys.map(key => caches.delete(key)));
}
window.location.reload();
/*
const cssFiles = [
'mapMAPCSS.css',
'main.css',
'leftside-modal.css'
];
cssFiles.forEach(file => {
const oldLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
.find(l => l.href.endsWith(file));
if (oldLink) {
const newLink = oldLink.cloneNode();
newLink.href = oldLink.href.split('?')[0] + '?view=map&_=' + new Date().getTime();
oldLink.parentNode.replaceChild(newLink, oldLink);
console.log(`CSS replaced: ${newLink.href}`);
} else {
console.warn(`CSS not found: ${file}`);
}
});
if ('caches' in window) {
const keys = await caches.keys();
console.log('Cache keys found:', keys);
await Promise.all(keys.map(key => caches.delete(key)));
console.log('Caches cleared');
}
console.log('CSS force refresh completed without page reload');
*/
}
$('.point-overlay').on(
'click mousedown mouseup touchstart touchend pointerdown pointerup',
function (e) {
e.stopPropagation();
}
);
$('.point-overlay *').on(
'click mousedown mouseup touchstart touchend pointerdown pointerup',
function (e) {
e.stopPropagation();
}
);
/**
* 1. IMAGE 모달이 열릴 때, 클릭한 버튼의 Customer ID를
* 주황색 [UPLOAD NEW IMAGE] 버튼에게 전달해주는 코드입니다.
*/
jQuery('#customerImageModal').on('show.bs.modal', function (event) {
var button = jQuery(event.relatedTarget); // 지도의 [IMAGE] 버튼
var cuid = button.data('cuid'); // 고객 번호 가져오기
// 모달 내부에 새로 만든 주황색 버튼에 ID를 심어줍니다.
jQuery('#btn-modal-upload-action').attr('data-cuid', cuid);
// (선택사항) 업체명 표시
var cname = button.data('cname');
jQuery('#imageCustomerName').text(cname);
});
/**
* 2. 주황색 버튼을 눌렀을 때 실제로 팝업창을 띄우는 함수입니다.
*/
function openCustomerImageUploadDirect(custUid) {
if (!custUid || custUid === 'undefined') {
alert("Customer ID를 찾을 수 없습니다.");
return;
}
var width = 600;
var height = 750;
var left = (window.screen.width / 2) - (width / 2);
var top = (window.screen.height / 2) - (height / 2);
// 경로와 변수명을 어제 확인한 /lib/ 폴더와 c_uid로 맞췄습니다.
var url = "/lib/customer_image_upload_popup.php?c_uid=" + custUid;
window.open(url, "uploadImage", "width=" + width + ",height=" + height + ",left=" + left + ",top=" + top + ",scrollbars=yes");
}
</script>