Add machine map editor and fix machine map display
- Create machine_map_editor.asp for bulk position editing - Draggable Leaflet markers for repositioning machines - Sidebar with filterable machine list - Pending changes tracking with save/reset functionality - Proper Y-axis coordinate conversion (2550 - top) - Update machine_map.asp - Reduce marker size from 20px to 12px to reduce overlap - Filter out network devices (16-20) and PCs (33+) from display - Show equipment only (machinetypeid 2-15) - Add updateMachinePositions API endpoint in api.asp - Accepts JSON array of position changes - Updates mapleft/maptop for multiple machines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
67
api.asp
67
api.asp
@@ -55,6 +55,8 @@ Select Case action
|
|||||||
GetShopfloorPCs()
|
GetShopfloorPCs()
|
||||||
Case "getRecordedIP"
|
Case "getRecordedIP"
|
||||||
GetRecordedIP()
|
GetRecordedIP()
|
||||||
|
Case "updateMachinePositions"
|
||||||
|
UpdateMachinePositions()
|
||||||
Case Else
|
Case Else
|
||||||
SendError "Invalid action: " & action
|
SendError "Invalid action: " & action
|
||||||
End Select
|
End Select
|
||||||
@@ -2412,4 +2414,69 @@ Sub UpdateWinRMStatus()
|
|||||||
Response.Write "{""success"":true,""message"":""WinRM status updated"",""hostname"":""" & hostname & """,""iswinrm"":" & winrmValue & "}"
|
Response.Write "{""success"":true,""message"":""WinRM status updated"",""hostname"":""" & hostname & """,""iswinrm"":" & winrmValue & "}"
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' ============================================================================
|
||||||
|
' UPDATE MACHINE POSITIONS - Bulk update mapleft/maptop for machines
|
||||||
|
' ============================================================================
|
||||||
|
Sub UpdateMachinePositions()
|
||||||
|
On Error Resume Next
|
||||||
|
|
||||||
|
Dim changesJson, changes, i, updateCount, errorCount
|
||||||
|
changesJson = Request.Form("changes")
|
||||||
|
|
||||||
|
If changesJson = "" Then
|
||||||
|
SendError "Missing changes parameter"
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
|
||||||
|
LogToFile "UpdateMachinePositions: Received " & Len(changesJson) & " bytes"
|
||||||
|
|
||||||
|
' Parse JSON array
|
||||||
|
changes = ParseJSONArray(changesJson)
|
||||||
|
|
||||||
|
If Not IsArray(changes) Then
|
||||||
|
SendError "Invalid changes format"
|
||||||
|
Exit Sub
|
||||||
|
End If
|
||||||
|
|
||||||
|
updateCount = 0
|
||||||
|
errorCount = 0
|
||||||
|
|
||||||
|
' Update each machine position
|
||||||
|
For i = 0 To UBound(changes)
|
||||||
|
Dim machineId, newLeft, newTop, updateSQL
|
||||||
|
|
||||||
|
machineId = GetJSONValue(changes(i), "id")
|
||||||
|
newLeft = GetJSONValue(changes(i), "newLeft")
|
||||||
|
newTop = GetJSONValue(changes(i), "newTop")
|
||||||
|
|
||||||
|
If machineId <> "" And IsNumeric(machineId) And IsNumeric(newLeft) And IsNumeric(newTop) Then
|
||||||
|
updateSQL = "UPDATE machines SET mapleft = " & CLng(newLeft) & ", maptop = " & CLng(newTop) & _
|
||||||
|
" WHERE machineid = " & CLng(machineId)
|
||||||
|
objConn.Execute updateSQL
|
||||||
|
|
||||||
|
If Err.Number = 0 Then
|
||||||
|
updateCount = updateCount + 1
|
||||||
|
LogToFile " Updated machineid " & machineId & " to (" & newLeft & ", " & newTop & ")"
|
||||||
|
Else
|
||||||
|
errorCount = errorCount + 1
|
||||||
|
LogToFile " Error updating machineid " & machineId & ": " & Err.Description
|
||||||
|
Err.Clear
|
||||||
|
End If
|
||||||
|
Else
|
||||||
|
errorCount = errorCount + 1
|
||||||
|
LogToFile " Invalid data for change " & i
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
LogToFile "UpdateMachinePositions: Updated " & updateCount & ", Errors " & errorCount
|
||||||
|
|
||||||
|
' Send response
|
||||||
|
Response.ContentType = "application/json"
|
||||||
|
If errorCount = 0 Then
|
||||||
|
Response.Write "{""success"":true,""message"":""Updated " & updateCount & " position(s)"",""updated"":" & updateCount & "}"
|
||||||
|
Else
|
||||||
|
Response.Write "{""success"":true,""message"":""Updated " & updateCount & ", " & errorCount & " error(s)"",""updated"":" & updateCount & ",""errors"":" & errorCount & "}"
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
|
||||||
%>
|
%>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ Do While Not rsLegend.EOF
|
|||||||
Case Else: legendColor = "#FFC107"
|
Case Else: legendColor = "#FFC107"
|
||||||
End Select
|
End Select
|
||||||
Response.Write("<div style='margin:8px 0; display:flex; align-items:center;'>")
|
Response.Write("<div style='margin:8px 0; display:flex; align-items:center;'>")
|
||||||
Response.Write("<span style='display:inline-block; width:16px; height:16px; background:" & legendColor & "; border-radius:50%; margin-right:10px; border:2px solid #fff; box-shadow:0 2px 5px rgba(0,0,0,0.5);'></span>")
|
Response.Write("<span style='display:inline-block; width:12px; height:12px; background:" & legendColor & "; border-radius:50%; margin-right:10px; border:1px solid #fff; box-shadow:0 1px 3px rgba(0,0,0,0.5);'></span>")
|
||||||
Response.Write("<span style='font-size:13px; color:#fff;'>" & Server.HTMLEncode(rsLegend("machinetype")) & "</span>")
|
Response.Write("<span style='font-size:13px; color:#fff;'>" & Server.HTMLEncode(rsLegend("machinetype")) & "</span>")
|
||||||
Response.Write("</div>")
|
Response.Write("</div>")
|
||||||
rsLegend.MoveNext
|
rsLegend.MoveNext
|
||||||
@@ -297,6 +297,8 @@ strSQL = "SELECT m.machineid, m.machinenumber, m.alias, m.serialnumber, " &_
|
|||||||
"AND m.isactive = 1 " &_
|
"AND m.isactive = 1 " &_
|
||||||
"AND m.mapleft IS NOT NULL " &_
|
"AND m.mapleft IS NOT NULL " &_
|
||||||
"AND m.maptop IS NOT NULL " &_
|
"AND m.maptop IS NOT NULL " &_
|
||||||
|
"AND mt.machinetypeid NOT IN (1, 16, 17, 18, 19, 20) " &_
|
||||||
|
"AND mt.machinetypeid < 33 " &_
|
||||||
"ORDER BY mt.machinetype, m.machinenumber ASC"
|
"ORDER BY mt.machinetype, m.machinenumber ASC"
|
||||||
|
|
||||||
Set rs = objConn.Execute(strSQL)
|
Set rs = objConn.Execute(strSQL)
|
||||||
@@ -385,12 +387,12 @@ Do While Not rs.EOF
|
|||||||
// Get color for this machine type
|
// Get color for this machine type
|
||||||
var color = machineTypeColors[machineTypeId] || machineTypeColors['default'];
|
var color = machineTypeColors[machineTypeId] || machineTypeColors['default'];
|
||||||
|
|
||||||
// Create custom marker icon
|
// Create custom marker icon (12px for less overlap)
|
||||||
var icon = L.divIcon({
|
var icon = L.divIcon({
|
||||||
html: '<div style="background:' + color + '; width:20px; height:20px; border-radius:50%; border:2px solid #fff; box-shadow:0 2px 5px rgba(0,0,0,0.5);"></div>',
|
html: '<div style="background:' + color + '; width:12px; height:12px; border-radius:50%; border:1px solid #fff; box-shadow:0 1px 3px rgba(0,0,0,0.5);"></div>',
|
||||||
iconSize: [20, 20],
|
iconSize: [12, 12],
|
||||||
iconAnchor: [10, 10],
|
iconAnchor: [6, 6],
|
||||||
popupAnchor: [0, -5],
|
popupAnchor: [0, -3],
|
||||||
className: 'custom-marker'
|
className: 'custom-marker'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
525
machine_map_editor.asp
Normal file
525
machine_map_editor.asp
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!--#include file="./includes/header.asp"-->
|
||||||
|
<!--#include file="./includes/sql.asp"-->
|
||||||
|
<title>Machine Map Editor - ShopDB</title>
|
||||||
|
<!-- Leaflet CSS (local copy like machine_map.asp) -->
|
||||||
|
<link rel="stylesheet" href="./leaflet/leaflet.css" />
|
||||||
|
<style>
|
||||||
|
.editor-container {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100vh - 180px);
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.map-panel {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
background: #1a1a2e;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #1a1a2e;
|
||||||
|
}
|
||||||
|
.sidebar-panel {
|
||||||
|
width: 320px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.machine-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #1a1a2e;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
.machine-list-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.machine-list-item:hover {
|
||||||
|
background: #2a2a4e;
|
||||||
|
}
|
||||||
|
.machine-list-item.selected {
|
||||||
|
background: #3a3a6e;
|
||||||
|
border-left: 3px solid #00ff00;
|
||||||
|
}
|
||||||
|
.machine-list-item.modified {
|
||||||
|
background: #3a3a2e;
|
||||||
|
}
|
||||||
|
.machine-list-item.no-position {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.machine-list-item .coords {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
.changes-panel {
|
||||||
|
background: #1a1a2e;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
.changes-count {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffcc00;
|
||||||
|
}
|
||||||
|
.btn-save-all {
|
||||||
|
background: linear-gradient(45deg, #28a745, #20c997);
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.btn-save-all:disabled {
|
||||||
|
background: #444;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.filter-box {
|
||||||
|
background: #1a1a2e;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
.instructions {
|
||||||
|
background: #2a2a4e;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
/* Custom marker styles */
|
||||||
|
.custom-marker-editor {
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
|
||||||
|
cursor: move !important;
|
||||||
|
}
|
||||||
|
.custom-marker-editor.selected {
|
||||||
|
border-color: #00ff00 !important;
|
||||||
|
box-shadow: 0 0 15px #00ff00 !important;
|
||||||
|
}
|
||||||
|
.custom-marker-editor.modified {
|
||||||
|
border-color: #ffcc00 !important;
|
||||||
|
}
|
||||||
|
.leaflet-marker-draggable {
|
||||||
|
cursor: move !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<%
|
||||||
|
theme = Request.Cookies("theme")
|
||||||
|
If theme = "" Then theme = "bg-theme1"
|
||||||
|
%>
|
||||||
|
<body class="bg-theme <%=theme%>">
|
||||||
|
|
||||||
|
<div id="wrapper">
|
||||||
|
<!--#include file="./includes/leftsidebar.asp"-->
|
||||||
|
<!--#include file="./includes/topbarheader.asp"-->
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="row pt-2 pb-2">
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<h4 class="page-title">Machine Map Editor</h4>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="index.asp">Dashboard</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="machine_map.asp">Machine Map</a></li>
|
||||||
|
<li class="breadcrumb-item active">Editor</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Editor Container -->
|
||||||
|
<div class="editor-container">
|
||||||
|
<!-- Map Panel -->
|
||||||
|
<div class="map-panel">
|
||||||
|
<div id="map"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar Panel -->
|
||||||
|
<div class="sidebar-panel">
|
||||||
|
<!-- Instructions -->
|
||||||
|
<div class="instructions">
|
||||||
|
<strong>Instructions:</strong><br>
|
||||||
|
- Click machine in list to select and center on map<br>
|
||||||
|
- Drag markers to reposition<br>
|
||||||
|
- Yellow border = modified<br>
|
||||||
|
- Click "Save All Changes" when done
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter -->
|
||||||
|
<div class="filter-box">
|
||||||
|
<input type="text" class="form-control form-control-sm" id="filterInput"
|
||||||
|
placeholder="Filter by machine number...">
|
||||||
|
<div class="mt-2">
|
||||||
|
<select class="form-control form-control-sm" id="typeFilter">
|
||||||
|
<option value="">All Types</option>
|
||||||
|
<%
|
||||||
|
Dim rsMT
|
||||||
|
Set rsMT = objConn.Execute("SELECT machinetypeid, machinetype FROM machinetypes WHERE machinetypeid < 16 AND machinetypeid > 1 ORDER BY machinetype")
|
||||||
|
Do While Not rsMT.EOF
|
||||||
|
Response.Write("<option value='" & rsMT("machinetypeid") & "'>" & Server.HTMLEncode(rsMT("machinetype")) & "</option>")
|
||||||
|
rsMT.MoveNext
|
||||||
|
Loop
|
||||||
|
rsMT.Close
|
||||||
|
Set rsMT = Nothing
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="text-light" style="font-size:12px;">
|
||||||
|
<input type="checkbox" id="showNoPosition"> Show machines without position
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Changes Panel -->
|
||||||
|
<div class="changes-panel">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span>Pending Changes:</span>
|
||||||
|
<span class="changes-count" id="changesCount">0</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success btn-save-all" id="saveAllBtn" disabled onclick="saveAllChanges()">
|
||||||
|
<i class="zmdi zmdi-check"></i> Save All Changes
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary btn-sm w-100 mt-2" onclick="resetAllChanges()">
|
||||||
|
<i class="zmdi zmdi-undo"></i> Reset All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Machine List -->
|
||||||
|
<div class="machine-list" id="machineList">
|
||||||
|
<!-- Populated by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="assets/js/jquery.min.js"></script>
|
||||||
|
<script src="assets/js/popper.min.js"></script>
|
||||||
|
<script src="assets/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/plugins/simplebar/js/simplebar.js"></script>
|
||||||
|
<script src="assets/js/sidebar-menu.js"></script>
|
||||||
|
<script src="assets/js/app-script.js"></script>
|
||||||
|
<!-- Leaflet JS (local copy like machine_map.asp) -->
|
||||||
|
<script src="./leaflet/leaflet.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Theme detection for map image
|
||||||
|
var theme = '<%=theme%>';
|
||||||
|
var lightThemes = ['bg-theme11', 'bg-theme13'];
|
||||||
|
var mapImageUrl = lightThemes.includes(theme) ? './images/sitemap2025-light.png' : './images/sitemap2025-dark.png';
|
||||||
|
|
||||||
|
// Machine type colors (exact copy from machine_map.asp)
|
||||||
|
var machineTypeColors = {
|
||||||
|
'1': '#4CAF50', // CNC
|
||||||
|
'2': '#2196F3', // Grinder
|
||||||
|
'3': '#FF9800', // Lathe
|
||||||
|
'4': '#F44336', // Mill
|
||||||
|
'5': '#9C27B0', // CMM
|
||||||
|
'6': '#00BCD4', // EDM
|
||||||
|
'7': '#E91E63', // Press
|
||||||
|
'8': '#607D8B', // Saw
|
||||||
|
'9': '#795548', // Welder
|
||||||
|
'10': '#FF5722', // Drill
|
||||||
|
'11': '#3F51B5', // Robot
|
||||||
|
'12': '#8BC34A', // Inspection
|
||||||
|
'13': '#CDDC39', // Assembly
|
||||||
|
'14': '#FFC107', // Other
|
||||||
|
'15': '#009688', // Wash
|
||||||
|
'default': '#FFC107'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Machine data from database
|
||||||
|
var machines = [
|
||||||
|
<%
|
||||||
|
Dim rsM, first
|
||||||
|
first = True
|
||||||
|
Set rsM = objConn.Execute("SELECT m.machineid, m.machinenumber, m.alias, m.mapleft, m.maptop, " &_
|
||||||
|
"mt.machinetypeid, mt.machinetype " &_
|
||||||
|
"FROM machines m " &_
|
||||||
|
"LEFT JOIN models mo ON m.modelnumberid = mo.modelnumberid " &_
|
||||||
|
"LEFT JOIN machinetypes mt ON mo.machinetypeid = mt.machinetypeid " &_
|
||||||
|
"WHERE m.pctypeid IS NULL " &_
|
||||||
|
"AND m.isactive = 1 " &_
|
||||||
|
"AND mt.machinetypeid NOT IN (1, 16, 17, 18, 19, 20) " &_
|
||||||
|
"AND mt.machinetypeid < 33 " &_
|
||||||
|
"ORDER BY m.machinenumber")
|
||||||
|
Do While Not rsM.EOF
|
||||||
|
If Not first Then Response.Write(",")
|
||||||
|
first = False
|
||||||
|
|
||||||
|
Dim ml, mt2, mnum, malias, mtid, mtname
|
||||||
|
ml = "null"
|
||||||
|
mt2 = "null"
|
||||||
|
If Not IsNull(rsM("mapleft")) Then ml = rsM("mapleft")
|
||||||
|
If Not IsNull(rsM("maptop")) Then mt2 = rsM("maptop")
|
||||||
|
mnum = Replace(rsM("machinenumber") & "", "\", "\\")
|
||||||
|
mnum = Replace(mnum, "'", "\'")
|
||||||
|
malias = Replace(rsM("alias") & "", "\", "\\")
|
||||||
|
malias = Replace(malias, "'", "\'")
|
||||||
|
mtid = 0
|
||||||
|
mtname = "Unknown"
|
||||||
|
If Not IsNull(rsM("machinetypeid")) Then mtid = rsM("machinetypeid")
|
||||||
|
If Not IsNull(rsM("machinetype")) Then mtname = Replace(rsM("machinetype") & "", "'", "\'")
|
||||||
|
|
||||||
|
Response.Write("{id:" & rsM("machineid") & ",num:'" & mnum & "',alias:'" & malias & "',")
|
||||||
|
Response.Write("left:" & ml & ",top:" & mt2 & ",typeId:" & mtid & ",type:'" & mtname & "'}")
|
||||||
|
|
||||||
|
rsM.MoveNext
|
||||||
|
Loop
|
||||||
|
rsM.Close
|
||||||
|
Set rsM = Nothing
|
||||||
|
%>
|
||||||
|
];
|
||||||
|
|
||||||
|
var map;
|
||||||
|
var markers = {};
|
||||||
|
var pendingChanges = {};
|
||||||
|
var selectedMachineId = null;
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
$(document).ready(function() {
|
||||||
|
initMap();
|
||||||
|
renderMachineList();
|
||||||
|
|
||||||
|
$('#filterInput').on('input', renderMachineList);
|
||||||
|
$('#typeFilter').on('change', renderMachineList);
|
||||||
|
$('#showNoPosition').on('change', renderMachineList);
|
||||||
|
});
|
||||||
|
|
||||||
|
function initMap() {
|
||||||
|
// Create map with simple CRS (same as machine_map.asp)
|
||||||
|
map = L.map('map', {
|
||||||
|
crs: L.CRS.Simple,
|
||||||
|
minZoom: -3,
|
||||||
|
maxZoom: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
var bounds = [[0, 0], [2550, 3300]];
|
||||||
|
L.imageOverlay(mapImageUrl, bounds).addTo(map);
|
||||||
|
map.fitBounds(bounds);
|
||||||
|
map.setView([1275, 1650], -2.3); // Match machine_map.asp zoom level
|
||||||
|
|
||||||
|
// Add markers for all machines with positions
|
||||||
|
machines.forEach(function(m) {
|
||||||
|
if (m.left !== null && m.top !== null) {
|
||||||
|
addMarker(m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMarker(machine) {
|
||||||
|
var color = machineTypeColors[machine.typeId] || machineTypeColors['default'];
|
||||||
|
|
||||||
|
var icon = L.divIcon({
|
||||||
|
html: '<div class="custom-marker-editor" data-id="' + machine.id + '" style="background:' + color + '; width:16px; height:16px;"></div>',
|
||||||
|
iconSize: [16, 16],
|
||||||
|
iconAnchor: [8, 8],
|
||||||
|
className: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert database Y (top-down) to Leaflet Y (bottom-up) - same as machine_map.asp
|
||||||
|
var leafletY = 2550 - machine.top;
|
||||||
|
var marker = L.marker([leafletY, machine.left], {
|
||||||
|
icon: icon,
|
||||||
|
draggable: true,
|
||||||
|
title: machine.num
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// Bind popup
|
||||||
|
marker.bindPopup('<strong>' + machine.num + '</strong><br>' + machine.type);
|
||||||
|
|
||||||
|
// Drag events
|
||||||
|
marker.on('dragend', function(e) {
|
||||||
|
var pos = e.target.getLatLng();
|
||||||
|
// Convert Leaflet Y (bottom-up) back to database Y (top-down)
|
||||||
|
var newTop = Math.round(2550 - pos.lat);
|
||||||
|
var newLeft = Math.round(pos.lng);
|
||||||
|
|
||||||
|
// Record change
|
||||||
|
pendingChanges[machine.id] = {
|
||||||
|
id: machine.id,
|
||||||
|
num: machine.num,
|
||||||
|
oldLeft: machine.left,
|
||||||
|
oldTop: machine.top,
|
||||||
|
newLeft: newLeft,
|
||||||
|
newTop: newTop
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update marker style
|
||||||
|
$(e.target._icon).find('.custom-marker-editor').addClass('modified');
|
||||||
|
|
||||||
|
updateChangesCount();
|
||||||
|
renderMachineList();
|
||||||
|
});
|
||||||
|
|
||||||
|
marker.on('click', function() {
|
||||||
|
selectMachine(machine.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
markers[machine.id] = marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMachineList() {
|
||||||
|
var list = $('#machineList');
|
||||||
|
list.empty();
|
||||||
|
|
||||||
|
var filter = $('#filterInput').val().toLowerCase();
|
||||||
|
var typeFilter = $('#typeFilter').val();
|
||||||
|
var showNoPosition = $('#showNoPosition').is(':checked');
|
||||||
|
|
||||||
|
machines.forEach(function(m) {
|
||||||
|
// Apply filters
|
||||||
|
if (filter && m.num.toLowerCase().indexOf(filter) === -1) return;
|
||||||
|
if (typeFilter && m.typeId != typeFilter) return;
|
||||||
|
|
||||||
|
var hasPosition = (m.left !== null && m.top !== null);
|
||||||
|
if (!hasPosition && !showNoPosition) return;
|
||||||
|
|
||||||
|
var currentLeft = m.left;
|
||||||
|
var currentTop = m.top;
|
||||||
|
if (pendingChanges[m.id]) {
|
||||||
|
currentLeft = pendingChanges[m.id].newLeft;
|
||||||
|
currentTop = pendingChanges[m.id].newTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
var coords = hasPosition ? '(' + currentLeft + ', ' + currentTop + ')' : 'No position';
|
||||||
|
var modified = pendingChanges[m.id] ? ' modified' : '';
|
||||||
|
var selected = (selectedMachineId === m.id) ? ' selected' : '';
|
||||||
|
var noPos = !hasPosition ? ' no-position' : '';
|
||||||
|
|
||||||
|
var item = $('<div class="machine-list-item' + modified + selected + noPos + '" data-id="' + m.id + '">' +
|
||||||
|
'<div><strong>' + m.num + '</strong><br><small class="text-muted">' + m.type + '</small></div>' +
|
||||||
|
'<div class="coords">' + coords + '</div></div>');
|
||||||
|
|
||||||
|
item.on('click', function() {
|
||||||
|
selectMachine(m.id);
|
||||||
|
if (hasPosition) {
|
||||||
|
// Use database top (convert to Leaflet Y for map view)
|
||||||
|
var dbTop = pendingChanges[m.id] ? pendingChanges[m.id].newTop : m.top;
|
||||||
|
var lng = pendingChanges[m.id] ? pendingChanges[m.id].newLeft : m.left;
|
||||||
|
var lat = 2550 - dbTop; // Convert to Leaflet coordinates
|
||||||
|
map.setView([lat, lng], 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
list.append(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectMachine(id) {
|
||||||
|
selectedMachineId = id;
|
||||||
|
|
||||||
|
// Update marker styles
|
||||||
|
$('.custom-marker-editor').removeClass('selected');
|
||||||
|
$('.custom-marker-editor[data-id="' + id + '"]').addClass('selected');
|
||||||
|
|
||||||
|
// Update list
|
||||||
|
$('.machine-list-item').removeClass('selected');
|
||||||
|
$('.machine-list-item[data-id="' + id + '"]').addClass('selected');
|
||||||
|
|
||||||
|
// Open popup
|
||||||
|
if (markers[id]) {
|
||||||
|
markers[id].openPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChangesCount() {
|
||||||
|
var count = Object.keys(pendingChanges).length;
|
||||||
|
$('#changesCount').text(count);
|
||||||
|
$('#saveAllBtn').prop('disabled', count === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAllChanges() {
|
||||||
|
var changes = Object.values(pendingChanges);
|
||||||
|
if (changes.length === 0) return;
|
||||||
|
|
||||||
|
$('#saveAllBtn').prop('disabled', true).html('<i class="zmdi zmdi-refresh zmdi-hc-spin"></i> Saving...');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'api.asp',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'updateMachinePositions',
|
||||||
|
changes: JSON.stringify(changes)
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
// Update local data
|
||||||
|
changes.forEach(function(c) {
|
||||||
|
var machine = machines.find(function(m) { return m.id === c.id; });
|
||||||
|
if (machine) {
|
||||||
|
machine.left = c.newLeft;
|
||||||
|
machine.top = c.newTop;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear modified styles
|
||||||
|
$('.custom-marker-editor').removeClass('modified');
|
||||||
|
|
||||||
|
pendingChanges = {};
|
||||||
|
updateChangesCount();
|
||||||
|
renderMachineList();
|
||||||
|
|
||||||
|
alert('Saved ' + changes.length + ' position(s) successfully!');
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (response.message || 'Failed to save'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
alert('Error saving changes: ' + error);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
$('#saveAllBtn').prop('disabled', Object.keys(pendingChanges).length === 0)
|
||||||
|
.html('<i class="zmdi zmdi-check"></i> Save All Changes');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAllChanges() {
|
||||||
|
if (Object.keys(pendingChanges).length === 0) return;
|
||||||
|
if (!confirm('Reset all pending changes?')) return;
|
||||||
|
|
||||||
|
// Reset marker positions
|
||||||
|
Object.keys(pendingChanges).forEach(function(id) {
|
||||||
|
var change = pendingChanges[id];
|
||||||
|
var machine = machines.find(function(m) { return m.id == id; });
|
||||||
|
if (machine && markers[id]) {
|
||||||
|
// Convert database Y to Leaflet Y
|
||||||
|
var leafletY = 2550 - machine.top;
|
||||||
|
markers[id].setLatLng([leafletY, machine.left]);
|
||||||
|
$(markers[id]._icon).find('.custom-marker-editor').removeClass('modified');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pendingChanges = {};
|
||||||
|
updateChangesCount();
|
||||||
|
renderMachineList();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<%objConn.Close%>
|
||||||
Reference in New Issue
Block a user