1855 lines
65 KiB
JavaScript
1855 lines
65 KiB
JavaScript
// App.js - FTTH Planner Application Logic
|
|
|
|
// Global variables
|
|
let editingItemId = null;
|
|
let tempClickLatLng = null;
|
|
|
|
|
|
// Initialize application
|
|
$(document).ready(function() {
|
|
//kirimNotifOdp(id)
|
|
loadFormData();
|
|
initializeEventListeners();
|
|
|
|
// Add core capacity change listener
|
|
$(document).on('change input', '#totalCoreCapacity, #coreUsed', calculateCoreAvailable);
|
|
});
|
|
|
|
// Load form data (tube colors, splitters)
|
|
function loadFormData() {
|
|
// Load tube colors
|
|
$.ajax({
|
|
url: 'api/tube_colors.php',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
// Populate tube color dropdown
|
|
let tubeColorSelect = $('#tubeColor');
|
|
tubeColorSelect.empty().append('<option value="">Pilih Warna Tube</option>');
|
|
|
|
// Populate core color dropdown
|
|
let coreColorSelect = $('#coreColor');
|
|
coreColorSelect.empty().append('<option value="">Pilih Warna Core</option>');
|
|
|
|
response.data.forEach(function(color) {
|
|
let option = `<option value="${color.id}" data-color="${color.hex_code}" style="border-left: 4px solid ${color.hex_code};">${color.color_name}</option>`;
|
|
tubeColorSelect.append(option);
|
|
coreColorSelect.append(option);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// Load splitter types
|
|
$.ajax({
|
|
url: 'api/splitters.php',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
let mainSplitterSelect = $('#splitterMain');
|
|
let odpSplitterSelect = $('#splitterOdp');
|
|
|
|
mainSplitterSelect.empty().append('<option value="">Pilih Splitter Utama</option>');
|
|
odpSplitterSelect.empty().append('<option value="">Pilih Splitter ODP</option>');
|
|
|
|
response.data.forEach(function(splitter) {
|
|
let option = `<option value="${splitter.id}">${splitter.ratio}</option>`;
|
|
|
|
if (splitter.type === 'main') {
|
|
mainSplitterSelect.append(option);
|
|
} else {
|
|
odpSplitterSelect.append(option);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize event listeners
|
|
function initializeEventListeners() {
|
|
// Item form submission
|
|
$('#itemForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
saveItem();
|
|
});
|
|
|
|
// Modal events
|
|
$('#itemModal').on('hidden.bs.modal', function() {
|
|
resetForm();
|
|
});
|
|
|
|
// Tube color change event to show color preview
|
|
$('#tubeColor').on('change', function() {
|
|
updateColorPreview();
|
|
});
|
|
}
|
|
|
|
// Show add item modal
|
|
function showAddItemModal(lat = null, lng = null) {
|
|
tempClickLatLng = lat && lng ? {lat: lat, lng: lng} : null;
|
|
|
|
$('#itemModalTitle').text('Tambah Item FTTH');
|
|
$('#itemId').val('');
|
|
editingItemId = null;
|
|
|
|
if (tempClickLatLng) {
|
|
$('#itemLat').val(tempClickLatLng.lat);
|
|
$('#itemLng').val(tempClickLatLng.lng);
|
|
}
|
|
|
|
$('#itemModal').modal('show');
|
|
}
|
|
|
|
// Add new item (from sidebar)
|
|
function addNewItem(itemType) {
|
|
showAddItemModal();
|
|
|
|
// Set item type based on parameter
|
|
let itemTypeId = getItemTypeId(itemType);
|
|
if (itemTypeId) {
|
|
$('#itemType').val(itemTypeId);
|
|
}
|
|
}
|
|
|
|
// Get item type ID from name
|
|
function getItemTypeId(typeName) {
|
|
switch(typeName) {
|
|
case 'OLT': return '1';
|
|
case 'Tiang Tumpu': return '2';
|
|
case 'ODP': return '3';
|
|
case 'ODC': return '4';
|
|
case 'Pelanggan': return '5';
|
|
default: return '';
|
|
}
|
|
}
|
|
|
|
// Edit existing item
|
|
//editan saya
|
|
function editItem(itemId) {
|
|
editingItemId = itemId;
|
|
|
|
$.ajax({
|
|
url: 'api/items.php',
|
|
method: 'GET',
|
|
data: { id: itemId },
|
|
success: function(response) {
|
|
if (response.success && response.data) {
|
|
let item = response.data;
|
|
|
|
$('#itemModalTitle').text('Edit Item FTTH');
|
|
$('#itemId').val(item.id);
|
|
$('#itemType').val(item.item_type_id);
|
|
$('#itemName').val(item.name);
|
|
$('#itemDescription').val(item.description);
|
|
$('#itemAddress').val(item.address);
|
|
$('#itemLat').val(item.latitude);
|
|
$('#itemLng').val(item.longitude);
|
|
$('#tubeColor').val(item.tube_color_id);
|
|
$('#coreColor').val(item.core_color_id);
|
|
$('#cableType').val(item.item_cable_type || 'distribution');
|
|
$('#totalCoreCapacity').val(item.total_core_capacity || 24);
|
|
$('#coreUsed').val(item.core_used);
|
|
$('#splitterMain').val(item.splitter_main_id);
|
|
$('#splitterOdp').val(item.splitter_odp_id);
|
|
$('#itemStatus').val(item.status);
|
|
$('#serial_number').val(item.serial_number);
|
|
$('#rx_power').val(item.rx_power || '');
|
|
|
|
// === Tambahan penting ===
|
|
toggleFields();
|
|
|
|
// Hitung core tersedia setelah isi coreUsed
|
|
setTimeout(() => calculateCoreAvailable(), 100);
|
|
|
|
updateColorPreview();
|
|
$('#itemModal').modal('show');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Error loading item data', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// Save item (create or update)
|
|
function saveItem() {
|
|
let method = editingItemId ? 'PUT' : 'POST';
|
|
|
|
// Validate required fields
|
|
if (!$('#itemType').val() || !$('#itemName').val()) {
|
|
showNotification('Harap isi semua field yang wajib', 'warning');
|
|
return;
|
|
}
|
|
|
|
// If no coordinates provided and not editing, get from temp click
|
|
if (!$('#itemLat').val() && !$('#itemLng').val() && tempClickLatLng) {
|
|
$('#itemLat').val(tempClickLatLng.lat);
|
|
$('#itemLng').val(tempClickLatLng.lng);
|
|
}
|
|
|
|
// Always use POST with FormData for compatibility
|
|
let formData = new FormData($('#itemForm')[0]);
|
|
|
|
// Log original method and current state
|
|
console.log('🔧 SAVEITEM DEBUG:');
|
|
console.log('Original method:', method);
|
|
console.log('editingItemId:', editingItemId);
|
|
console.log('Item ID field value:', $('#itemId').val());
|
|
|
|
// For PUT requests, add _method parameter
|
|
if (method === 'PUT') {
|
|
formData.append('_method', 'PUT');
|
|
|
|
// Ensure ID is included for PUT request
|
|
if (editingItemId && !formData.get('id')) {
|
|
formData.set('id', editingItemId);
|
|
}
|
|
|
|
// Also ensure we have the ID from the hidden field
|
|
if ($('#itemId').val() && !formData.get('id')) {
|
|
formData.set('id', $('#itemId').val());
|
|
}
|
|
|
|
// Log all data being sent
|
|
console.log('🚀 PUT Data being sent (all fields):');
|
|
for (let pair of formData.entries()) {
|
|
console.log(' ' + pair[0] + ': ' + pair[1]);
|
|
}
|
|
} else {
|
|
console.log('🚀 POST Data being sent (new item)');
|
|
}
|
|
|
|
// Force POST method with explicit type declaration
|
|
let requestConfig = {
|
|
url: 'api/items.php',
|
|
type: 'POST', // Use 'type' instead of 'method' for better compatibility
|
|
method: 'POST', // Also set method for newer jQuery versions
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
dataType: 'json',
|
|
cache: false, // Disable caching
|
|
success: function(response) {
|
|
if (response && response.success) {
|
|
$('#itemModal').modal('hide');
|
|
|
|
if (editingItemId) {
|
|
// Update existing marker
|
|
updateMarker(editingItemId, response.data);
|
|
showNotification('Item berhasil diupdate', 'success');
|
|
} else {
|
|
// Add new marker
|
|
addMarkerToMap(response.data);
|
|
showNotification('Item berhasil ditambahkan', 'success');
|
|
}
|
|
|
|
updateStatistics();
|
|
} else {
|
|
showNotification(response?.message || 'Error saving item', 'error');
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('AJAX Error:', error, xhr.responseText);
|
|
console.error('Response Text:', xhr.responseText);
|
|
showNotification('Error saving item: ' + error, 'error');
|
|
}
|
|
};
|
|
|
|
console.log('🚀 Final request config:', {
|
|
url: requestConfig.url,
|
|
type: requestConfig.type,
|
|
method: requestConfig.method,
|
|
dataType: requestConfig.dataType
|
|
});
|
|
|
|
$.ajax(requestConfig);
|
|
}
|
|
|
|
// Update marker on map
|
|
function updateMarker(itemId, itemData) {
|
|
if (markers[itemId]) {
|
|
// Remove old marker
|
|
map.removeLayer(markers[itemId]);
|
|
delete markers[itemId];
|
|
}
|
|
|
|
// Add updated marker
|
|
addMarkerToMap(itemData);
|
|
}
|
|
|
|
// Delete item
|
|
function deleteItem(itemId) {
|
|
if (confirm('Apakah Anda yakin ingin menghapus item ini?')) {
|
|
$.ajax({
|
|
url: 'api/items.php',
|
|
method: 'DELETE',
|
|
data: { id: itemId },
|
|
success: function(response) {
|
|
if (response.success) {
|
|
// Remove marker from map
|
|
if (markers[itemId]) {
|
|
map.removeLayer(markers[itemId]);
|
|
delete markers[itemId];
|
|
}
|
|
|
|
// Remove any routes connected to this item
|
|
removeRoutesForItem(itemId);
|
|
|
|
showNotification('Item berhasil dihapus', 'success');
|
|
updateStatistics();
|
|
} else {
|
|
showNotification(response.message || 'Error deleting item', 'error');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Error deleting item', 'error');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Remove routes connected to item
|
|
function removeRoutesForItem(itemId) {
|
|
$.ajax({
|
|
url: 'api/routes.php',
|
|
method: 'DELETE',
|
|
data: { item_id: itemId },
|
|
success: function(response) {
|
|
if (response.success && response.deleted_routes) {
|
|
response.deleted_routes.forEach(function(routeId) {
|
|
if (routes[routeId]) {
|
|
map.removeLayer(routes[routeId]);
|
|
delete routes[routeId];
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Reset form
|
|
function resetForm() {
|
|
$('#itemForm')[0].reset();
|
|
$('#itemId').val('');
|
|
editingItemId = null;
|
|
tempClickLatLng = null;
|
|
updateColorPreview();
|
|
}
|
|
|
|
// Update color preview
|
|
function updateColorPreview() {
|
|
let selectedColor = $('#tubeColor option:selected').data('color');
|
|
if (selectedColor) {
|
|
$('#tubeColor').css('border-left', `5px solid ${selectedColor}`);
|
|
} else {
|
|
$('#tubeColor').css('border-left', 'none');
|
|
}
|
|
}
|
|
|
|
// Show item list
|
|
function showItemList() {
|
|
$.ajax({
|
|
url: 'api/items.php',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
let itemListHtml = generateItemListHtml(response.data);
|
|
showModal('Daftar Item FTTH', itemListHtml, 'modal-xl');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Error loading item list', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
function showLaporanOdp() {
|
|
$.ajax({
|
|
url: 'api/laporan_odp.php',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
if (response.data) {
|
|
let html = generateLaporanOdpHtml(response.data);
|
|
showModal('Laporan Kualitas ODP', html, 'modal-xl');
|
|
} else {
|
|
showNotification('Data tidak tersedia', 'error');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Gagal memuat laporan ODP', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
function generateLaporanOdpHtml(data) {
|
|
let html = `
|
|
<table class="table table-bordered table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Nama ODP</th>
|
|
<th>Total Client</th>
|
|
<th>AVG RX</th>
|
|
<th>Client Buruk</th>
|
|
<th>Status</th>
|
|
<th>Map</th>
|
|
<th>Notif</th> <!-- 🔥 TAMBAHAN -->
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
data.forEach(item => {
|
|
|
|
let iconColor = 'green';
|
|
if (item.status === 'WARNING') iconColor = 'orange';
|
|
if (item.status === 'BURUK') iconColor = 'red';
|
|
if (item.status === 'KRITIS') iconColor = 'darkred';
|
|
|
|
let rxText = '';
|
|
let badClientText = '';
|
|
|
|
if (item.analisa.includes('RX:')) {
|
|
let parts = item.analisa.split('|');
|
|
|
|
parts.forEach(p => {
|
|
if (p.includes('RX:')) {
|
|
rxText = p.replace('RX:', '').trim();
|
|
}
|
|
if (p.includes('Client bermasalah')) {
|
|
badClientText = p.replace('Client bermasalah:', '').trim();
|
|
}
|
|
});
|
|
}
|
|
|
|
html += `
|
|
<tr>
|
|
|
|
<td>
|
|
<b>${item.nama}</b><br>
|
|
|
|
<small style="color:gray">
|
|
${item.analisa.split('|')[0]}
|
|
</small>
|
|
|
|
${rxText ? `<br><small>RX: ${rxText}</small>` : ''}
|
|
|
|
${badClientText ? `
|
|
<br><small style="color:red">
|
|
⚠ ${badClientText}
|
|
</small>` : ''}
|
|
</td>
|
|
|
|
<td>${item.total}</td>
|
|
|
|
<td>${item.avg_rx ?? '-'} dBm</td>
|
|
|
|
<td>${item.bad_count}</td>
|
|
|
|
<td>
|
|
<span class="badge badge-${item.status_color}">
|
|
${item.status}
|
|
</span>
|
|
</td>
|
|
|
|
<td style="text-align:center;">
|
|
<i class="fas fa-map-marker-alt"
|
|
style="color:${iconColor}; cursor:pointer; font-size:18px;"
|
|
onclick="focusOnItem(${item.id})"
|
|
title="Lihat lokasi ODP">
|
|
</i>
|
|
</td>
|
|
|
|
<!-- 🔥 TOMBOL KIRIM WA -->
|
|
<td style="text-align:center;">
|
|
<button class="btn btn-sm btn-success"
|
|
onclick="kirimNotifOdp(${item.id})"
|
|
title="Kirim notifikasi ke teknisi">
|
|
<i class="fas fa-paper-plane"></i>
|
|
</button>
|
|
</td>
|
|
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
html += `</tbody></table>`;
|
|
|
|
return html;
|
|
}
|
|
|
|
function kirimNotifOdp(id) {
|
|
console.log("Kirim ODP ID:", id);
|
|
|
|
fetch(`api/laporan_odp.php?kirim=1&id=${id}`)
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
if (res.success) {
|
|
alert("✅ Notif terkirim ke group");
|
|
} else {
|
|
alert("❌ Gagal kirim");
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
alert("❌ Error koneksi");
|
|
});
|
|
}
|
|
// Generate item list HTML
|
|
// Generate item list HTML
|
|
function generateItemListHtml(items) {
|
|
let html = `
|
|
<div class="mb-3">
|
|
<input type="text" id="searchInput" class="form-control" placeholder="Cari data...">
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover" id="itemTable">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Jenis</th>
|
|
<th>Nama</th>
|
|
<th>Alamat</th>
|
|
<th>Koordinat</th>
|
|
<th>Status</th>
|
|
<th>Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
items.forEach(function(item) {
|
|
html += `
|
|
<tr>
|
|
<td>
|
|
<i class="${getItemIcon(item.item_type_name)}" style="color: ${getItemColor(item.item_type_name)};"></i>
|
|
${item.item_type_name}
|
|
</td>
|
|
<td>${item.name}</td>
|
|
<td>${item.address || '-'}</td>
|
|
<td>${(isNaN(parseFloat(item.latitude)) || isNaN(parseFloat(item.longitude))) ? 'Koordinat tidak valid' : `${parseFloat(item.latitude).toFixed(6)}, ${parseFloat(item.longitude).toFixed(6)}`}</td>
|
|
<td>
|
|
<span class="badge badge-${getStatusBadgeClass(item.status)}">
|
|
${getStatusText(item.status)}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-primary" onclick="editItem(${item.id}); $('#genericModal').modal('hide');">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-info" onclick="focusOnItem(${item.id}); $('#genericModal').modal('hide');">
|
|
<i class="fas fa-map-marker-alt"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="deleteItem(${item.id}); $('#genericModal').modal('hide');">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById("searchInput").addEventListener("keyup", function() {
|
|
let filter = this.value.toLowerCase();
|
|
let rows = document.querySelectorAll("#itemTable tbody tr");
|
|
rows.forEach(row => {
|
|
let text = row.innerText.toLowerCase();
|
|
row.style.display = text.includes(filter) ? "" : "none";
|
|
});
|
|
});
|
|
</script>
|
|
`;
|
|
|
|
return html;
|
|
}
|
|
|
|
|
|
// Get item color
|
|
function getItemColor(typeName) {
|
|
switch(typeName) {
|
|
case 'OLT': return '#FF6B6B';
|
|
case 'Tiang Tumpu': return '#4ECDC4';
|
|
case 'ODP': return '#45B7D1';
|
|
case 'ODC': return '#96CEB4';
|
|
case 'Pelanggan': return '#FFA500';
|
|
default: return '#999';
|
|
}
|
|
}
|
|
|
|
// Focus on item in map
|
|
function focusOnItem(itemId) {
|
|
if (markers[itemId]) {
|
|
let marker = markers[itemId];
|
|
map.setView(marker.getLatLng(), 16);
|
|
marker.openPopup();
|
|
}
|
|
}
|
|
|
|
// Show route list
|
|
function showRouteList() {
|
|
$.ajax({
|
|
url: 'api/routes.php',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
let routeListHtml = generateRouteListHtml(response.data);
|
|
showModal('Daftar Routing Kabel', routeListHtml, 'modal-xl');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Error loading route list', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Generate route list HTML
|
|
function generateRouteListHtml(routes) {
|
|
let html = `
|
|
<div class="mb-3">
|
|
<input type="text" id="searchRouteInput" class="form-control" placeholder="Cari data route...">
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover" id="routeTable">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Dari</th>
|
|
<th>Ke</th>
|
|
<th>Jarak</th>
|
|
<th>Tipe Kabel</th>
|
|
<th>Core</th>
|
|
<th>Status</th>
|
|
<th>Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
routes.forEach(function(route) {
|
|
let distance = route.distance ? (route.distance / 1000).toFixed(2) + ' km' : '-';
|
|
|
|
html += `
|
|
<tr>
|
|
<td>${route.from_item_name || 'Unknown'}</td>
|
|
<td>${route.to_item_name || 'Unknown'}</td>
|
|
<td>${distance}</td>
|
|
<td>${route.cable_type || '-'}</td>
|
|
<td>${route.core_count || '-'}</td>
|
|
<td>
|
|
<span class="badge badge-${getStatusBadgeClass(route.status)}">
|
|
${getStatusText(route.status)}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-primary" onclick="editRoute(${route.id}); $('#genericModal').modal('hide');" title="Edit Route">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-info" onclick="focusOnRoute(${route.id}); $('#genericModal').modal('hide');" title="Lihat di Peta">
|
|
<i class="fas fa-map-marker-alt"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="deleteRoute(${route.id}); $('#genericModal').modal('hide');" title="Hapus Route">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById("searchRouteInput").addEventListener("keyup", function() {
|
|
let filter = this.value.toLowerCase();
|
|
let rows = document.querySelectorAll("#routeTable tbody tr");
|
|
rows.forEach(row => {
|
|
let text = row.innerText.toLowerCase();
|
|
row.style.display = text.includes(filter) ? "" : "none";
|
|
});
|
|
});
|
|
</script>
|
|
`;
|
|
|
|
return html;
|
|
}
|
|
|
|
|
|
// Focus on route in map
|
|
function focusOnRoute(routeId) {
|
|
if (routes[routeId]) {
|
|
let route = routes[routeId];
|
|
map.fitBounds(route.getBounds());
|
|
route.openPopup();
|
|
}
|
|
}
|
|
|
|
// Delete route
|
|
function deleteRoute(routeId) {
|
|
if (confirm('Apakah Anda yakin ingin menghapus route ini?')) {
|
|
$.ajax({
|
|
url: 'api/routes.php',
|
|
method: 'DELETE',
|
|
data: { id: routeId },
|
|
success: function(response) {
|
|
if (response.success) {
|
|
if (routes[routeId]) {
|
|
map.removeLayer(routes[routeId]);
|
|
delete routes[routeId];
|
|
}
|
|
showNotification('Route berhasil dihapus', 'success');
|
|
} else {
|
|
showNotification(response.message || 'Error deleting route', 'error');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Error deleting route', 'error');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Generic modal function
|
|
function showModal(title, content, size = 'modal-lg') {
|
|
if (!$('#genericModal').length) {
|
|
$('body').append(`
|
|
<div class="modal fade" id="genericModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog ${size}" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title" id="genericModalTitle"></h4>
|
|
<button type="button" class="close" data-dismiss="modal">
|
|
<span>×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body" id="genericModalBody">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Tutup</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
$('#genericModalTitle').text(title);
|
|
$('#genericModalBody').html(content);
|
|
$('#genericModal').modal('show');
|
|
}
|
|
|
|
// Edit route function
|
|
function editRoute(routeId) {
|
|
// Get route data first
|
|
$.ajax({
|
|
url: 'api/routes.php',
|
|
method: 'GET',
|
|
data: { id: routeId },
|
|
success: function(response) {
|
|
if (response.success && response.data) {
|
|
let route = response.data;
|
|
showEditRouteModal(route);
|
|
} else {
|
|
showNotification('Error loading route data', 'error');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Error loading route data', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
//terpasang kabel rx power
|
|
// Contoh marker pelanggan
|
|
// Event saat dropdown status diubah
|
|
// Fungsi untuk memuat RX Power di tengah kabel
|
|
|
|
|
|
// Show edit route modal
|
|
function showEditRouteModal(route) {
|
|
let modalHtml = `
|
|
<form id="editRouteForm">
|
|
<input type="hidden" id="editRouteId" value="${route.id}">
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label>Dari Item</label>
|
|
<input type="text" class="form-control" value="${route.from_item_name}" readonly>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label>Ke Item</label>
|
|
<input type="text" class="form-control" value="${route.to_item_name}" readonly>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="form-group">
|
|
<label for="editCableType">Tipe Kabel</label>
|
|
<select class="form-control" id="editCableType" name="cable_type">
|
|
<option value="Fiber Optic" ${route.cable_type === 'Fiber Optic' ? 'selected' : ''}>Fiber Optic</option>
|
|
<option value="ADSS" ${route.cable_type === 'ADSS' ? 'selected' : ''}>ADSS (All Dielectric Self-Supporting)</option>
|
|
<option value="OPGW" ${route.cable_type === 'OPGW' ? 'selected' : ''}>OPGW (Optical Ground Wire)</option>
|
|
<option value="Armored" ${route.cable_type === 'Armored' ? 'selected' : ''}>Armored Fiber</option>
|
|
<option value="Indoor" ${route.cable_type === 'Indoor' ? 'selected' : ''}>Indoor Fiber</option>
|
|
<option value="Outdoor" ${route.cable_type === 'Outdoor' ? 'selected' : ''}>Outdoor Fiber</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-group">
|
|
<label for="editCoreCount">Jumlah Core</label>
|
|
<select class="form-control" id="editCoreCount" name="core_count">
|
|
<option value="2" ${route.core_count == 2 ? 'selected' : ''}>2 Core</option>
|
|
<option value="4" ${route.core_count == 4 ? 'selected' : ''}>4 Core</option>
|
|
<option value="6" ${route.core_count == 6 ? 'selected' : ''}>6 Core</option>
|
|
<option value="8" ${route.core_count == 8 ? 'selected' : ''}>8 Core</option>
|
|
<option value="12" ${route.core_count == 12 ? 'selected' : ''}>12 Core</option>
|
|
<option value="24" ${route.core_count == 24 ? 'selected' : ''}>24 Core</option>
|
|
<option value="48" ${route.core_count == 48 ? 'selected' : ''}>48 Core</option>
|
|
<option value="72" ${route.core_count == 72 ? 'selected' : ''}>72 Core</option>
|
|
<option value="96" ${route.core_count == 96 ? 'selected' : ''}>96 Core</option>
|
|
<option value="144" ${route.core_count == 144 ? 'selected' : ''}>144 Core</option>
|
|
<option value="216" ${route.core_count == 216 ? 'selected' : ''}>216 Core</option>
|
|
<option value="288" ${route.core_count == 288 ? 'selected' : ''}>288 Core</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="form-group">
|
|
<label for="editRouteStatus">Status</label>
|
|
<select class="form-control" id="editRouteStatus" name="status">
|
|
<option value="planned" ${route.status === 'planned' ? 'selected' : ''}>Perencanaan</option>
|
|
<option value="installed" ${route.status === 'installed' ? 'selected' : ''}>Terpasang</option>
|
|
<option value="maintenance" ${route.status === 'maintenance' ? 'selected' : ''}>Maintenance</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Jarak</label>
|
|
<input type="text" class="form-control" value="${route.distance ? (route.distance / 1000).toFixed(2) + ' km' : 'N/A'}" readonly>
|
|
<small class="text-muted">Jarak dihitung otomatis berdasarkan routing</small>
|
|
</div>
|
|
|
|
<div class="text-right">
|
|
<button type="button" class="btn btn-secondary" onclick="$('#routeEditModal').modal('hide')">Batal</button>
|
|
<button type="submit" class="btn btn-primary">Update Route</button>
|
|
</div>
|
|
</form>
|
|
`;
|
|
|
|
// Create modal if doesn't exist
|
|
if (!$('#routeEditModal').length) {
|
|
$('body').append(`
|
|
<div class="modal fade" id="routeEditModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog modal-lg" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">Edit Routing Kabel</h4>
|
|
<button type="button" class="close" data-dismiss="modal">
|
|
<span>×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body" id="routeEditModalBody">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
$('#routeEditModalBody').html(modalHtml);
|
|
$('#routeEditModal').modal('show');
|
|
|
|
// Handle form submission
|
|
$('#editRouteForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
saveRouteEdit();
|
|
});
|
|
}
|
|
|
|
// Save route edit
|
|
function saveRouteEdit() {
|
|
let formData = new FormData();
|
|
formData.append('_method', 'PUT');
|
|
formData.append('id', $('#editRouteId').val());
|
|
formData.append('cable_type', $('#editCableType').val());
|
|
formData.append('core_count', $('#editCoreCount').val());
|
|
formData.append('status', $('#editRouteStatus').val());
|
|
|
|
console.log('🚀 Route Edit Data being sent:');
|
|
for (let pair of formData.entries()) {
|
|
console.log(' ' + pair[0] + ': ' + pair[1]);
|
|
}
|
|
|
|
$.ajax({
|
|
url: 'api/routes.php',
|
|
type: 'POST',
|
|
method: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
dataType: 'json',
|
|
cache: false,
|
|
success: function(response) {
|
|
console.log('✅ Route update response:', response);
|
|
if (response.success) {
|
|
$('#routeEditModal').modal('hide');
|
|
showNotification('Route berhasil diupdate', 'success');
|
|
|
|
// Refresh route list if open
|
|
if ($('#genericModal').hasClass('show')) {
|
|
showRouteList();
|
|
}
|
|
|
|
// Update route on map
|
|
loadRoutes();
|
|
} else {
|
|
console.error('❌ Route update failed:', response.message);
|
|
showNotification(response.message || 'Error updating route', 'error');
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('❌ AJAX Error:', error, xhr.responseText);
|
|
console.error('Response Text:', xhr.responseText);
|
|
try {
|
|
let errorResponse = JSON.parse(xhr.responseText);
|
|
showNotification(errorResponse.message || 'Error updating route', 'error');
|
|
} catch(e) {
|
|
showNotification('Error updating route: ' + error, 'error');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Calculate core available
|
|
function calculateCoreAvailable() {
|
|
let totalCapacity = parseInt($('#totalCoreCapacity').val()) || 0;
|
|
let coreUsed = parseInt($('#coreUsed').val()) || 0;
|
|
let coreAvailable = totalCapacity - coreUsed;
|
|
|
|
$('#coreAvailable').val(coreAvailable + ' / ' + totalCapacity + ' Core');
|
|
|
|
// Set color based on availability
|
|
if (coreAvailable <= 0) {
|
|
$('#coreAvailable').removeClass('text-success text-warning').addClass('text-danger');
|
|
} else if (coreAvailable <= totalCapacity * 0.2) {
|
|
$('#coreAvailable').removeClass('text-success text-danger').addClass('text-warning');
|
|
} else {
|
|
$('#coreAvailable').removeClass('text-danger text-warning').addClass('text-success');
|
|
}
|
|
}
|
|
|
|
// Sync core usage from routes
|
|
function syncCoreUsageFromRoutes(itemId) {
|
|
if (!itemId) return;
|
|
|
|
$.ajax({
|
|
url: 'api/routes.php',
|
|
method: 'GET',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
let totalCoreUsed = 0;
|
|
|
|
response.data.forEach(function(route) {
|
|
if (route.from_item_id == itemId || route.to_item_id == itemId) {
|
|
totalCoreUsed += parseInt(route.core_count) || 0;
|
|
}
|
|
});
|
|
|
|
// Update core used in form
|
|
$('#coreUsed').val(totalCoreUsed);
|
|
calculateCoreAvailable();
|
|
|
|
console.log(`📊 Core usage synced for item ${itemId}: ${totalCoreUsed} cores used`);
|
|
}
|
|
},
|
|
error: function() {
|
|
console.error('Failed to sync core usage from routes');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Enhanced edit item to include core sync
|
|
function editItemEnhanced(itemId) {
|
|
editItem(itemId);
|
|
// Sync core usage after loading item data
|
|
setTimeout(() => syncCoreUsageFromRoutes(itemId), 500);
|
|
}
|
|
|
|
// Show item detail
|
|
function showItemDetail(itemId) {
|
|
$.ajax({
|
|
url: 'api/items.php',
|
|
method: 'GET',
|
|
data: { id: itemId },
|
|
success: function(response) {
|
|
if (response.success && response.data) {
|
|
let item = response.data;
|
|
showItemDetailModal(item);
|
|
} else {
|
|
showNotification('Error loading item data', 'error');
|
|
}
|
|
},
|
|
error: function() {
|
|
showNotification('Error loading item data', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Show item detail modal //ini detail jika mau edit
|
|
function showItemDetailModal(item) {
|
|
let modalHtml = `
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0">
|
|
<i class="${getItemIcon(item.item_type_name)}"></i>
|
|
${item.name}
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<!-- Basic Information -->
|
|
<div class="col-md-6">
|
|
<h6 class="text-primary mb-3">
|
|
<i class="fas fa-info-circle"></i> Informasi Dasar
|
|
</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>ID:</strong></td>
|
|
<td>${item.id}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Jenis Item:</strong></td>
|
|
<td>
|
|
<span class="badge badge-primary">
|
|
<i class="${getItemIcon(item.item_type_name)}"></i>
|
|
${item.item_type_name}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Nama:</strong></td>
|
|
<td>${item.name}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Deskripsi:</strong></td>
|
|
<td>${item.description || '-'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Alamat:</strong></td>
|
|
<td>${item.address || '-'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Status:</strong></td>
|
|
<td>
|
|
<span class="badge badge-${getStatusBadgeClass(item.status)}">
|
|
${getStatusText(item.status)}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Location Information -->
|
|
<div class="col-md-6">
|
|
<h6 class="text-success mb-3">
|
|
<i class="fas fa-map-marker-alt"></i> Informasi Lokasi
|
|
</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Latitude:</strong></td>
|
|
<td>${item.latitude}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Longitude:</strong></td>
|
|
<td>${item.longitude}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Koordinat:</strong></td>
|
|
<td>
|
|
<code>${item.latitude}, ${item.longitude}</code>
|
|
<button class="btn btn-sm btn-outline-secondary ml-2" onclick="copyToClipboard('${item.latitude}, ${item.longitude}')" title="Copy Koordinat">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Google Maps:</strong></td>
|
|
<td>
|
|
<a href="https://maps.google.com/?q=${item.latitude},${item.longitude}" target="_blank" class="btn btn-sm btn-outline-primary">
|
|
<i class="fas fa-external-link-alt"></i> Buka di Maps
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="row">
|
|
<!-- Core & Cable Information -->
|
|
<div class="col-md-6">
|
|
<h6 class="text-warning mb-3">
|
|
<i class="fas fa-network-wired"></i> Informasi Core & Kabel
|
|
</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Warna Tube:</strong></td>
|
|
<td>
|
|
${item.tube_color_name ? `
|
|
<span class="color-box" style="background-color: ${item.hex_code}; width: 20px; height: 20px; display: inline-block; margin-right: 8px; border: 1px solid #ccc;"></span>
|
|
${item.tube_color_name}
|
|
` : '-'}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Warna Core:</strong></td>
|
|
<td>
|
|
${item.core_color_name ? `
|
|
<span class="color-box" style="background-color: ${item.core_hex_code}; width: 20px; height: 20px; display: inline-block; margin-right: 8px; border: 1px solid #ccc;"></span>
|
|
${item.core_color_name}
|
|
` : '-'}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Jenis Kabel:</strong></td>
|
|
<td>
|
|
${item.item_cable_type ? `
|
|
<span class="badge badge-${getCableTypeBadge(item.item_cable_type)}">
|
|
${getCableTypeText(item.item_cable_type)}
|
|
</span>
|
|
` : '-'}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Kapasitas Core:</strong></td>
|
|
<td>
|
|
<span class="badge badge-secondary">${item.total_core_capacity || 24} Core</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Core Digunakan:</strong></td>
|
|
<td>
|
|
<span class="badge badge-${getCoreUsageBadge(item.core_used, item.total_core_capacity)}">
|
|
${item.core_used || 0} Core
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Core Tersedia:</strong></td>
|
|
<td>
|
|
<span class="badge badge-${getCoreUsageBadge(item.core_used, item.total_core_capacity)}">
|
|
${(item.total_core_capacity || 24) - (item.core_used || 0)} Core
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Splitter Information -->
|
|
<div class="col-md-6">
|
|
<h6 class="text-danger mb-3">
|
|
<i class="fas fa-project-diagram"></i> Informasi Splitter
|
|
</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Splitter Utama:</strong></td>
|
|
<td>
|
|
${item.splitter_main_ratio ? `
|
|
<span class="badge badge-info">${item.splitter_main_ratio}</span>
|
|
` : '-'}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Splitter ODP:</strong></td>
|
|
<td>
|
|
${item.splitter_odp_ratio ? `
|
|
<span class="badge badge-warning">${item.splitter_odp_ratio}</span>
|
|
` : '-'}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h6 class="text-secondary mb-3 mt-4">
|
|
<i class="fas fa-clock"></i> Timestamp
|
|
</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Dibuat:</strong></td>
|
|
<td>${formatDate(item.created_at)}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Diupdate:</strong></td>
|
|
<td>${formatDate(item.updated_at)}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer text-right">
|
|
<button type="button" class="btn btn-secondary" onclick="$('#itemDetailModal').modal('hide')">
|
|
<i class="fas fa-times"></i> Tutup
|
|
</button>
|
|
<button type="button" class="btn btn-primary" onclick="editItem(${item.id}); $('#itemDetailModal').modal('hide');">
|
|
<i class="fas fa-edit"></i> Edit Item
|
|
</button>
|
|
<button type="button" class="btn btn-success" onclick="focusOnItem(${item.id}); $('#itemDetailModal').modal('hide');">
|
|
<i class="fas fa-crosshairs"></i> Fokus di Peta
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Create modal if doesn't exist
|
|
if (!$('#itemDetailModal').length) {
|
|
$('body').append(`
|
|
<div class="modal fade" id="itemDetailModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog modal-xl" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">
|
|
<i class="fas fa-info-circle"></i> Detail Item FTTH
|
|
</h4>
|
|
<button type="button" class="close" data-dismiss="modal">
|
|
<span>×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body" id="itemDetailModalBody">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
$('#itemDetailModalBody').html(modalHtml);
|
|
$('#itemDetailModal').modal('show');
|
|
}
|
|
|
|
//detailredaman
|
|
// Fungsi untuk menampilkan detail redaman
|
|
function showRedamanDetail(item) {
|
|
let modalHtml = `
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-tachometer-alt"></i> Detail Redaman
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Serial Number (SN):</strong></td>
|
|
<td>${item.serial_number}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Rx Power:</strong></td>
|
|
<td>${item.rx_power}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>PPPoE IP:</strong></td>
|
|
<td>
|
|
${item.pppoe_ip && item.pppoe_ip !== 'Tidak tersedia'
|
|
? `<a href="http://${item.pppoe_ip}" target="_blank">${item.pppoe_ip}</a>`
|
|
: item.pppoe_ip}
|
|
</td>
|
|
</tr>
|
|
|
|
</table>
|
|
</div>
|
|
<div class="card-footer text-right">
|
|
<button type="button" class="btn btn-secondary" onclick="$('#redamanDetailModal').modal('hide')">
|
|
<i class="fas fa-times"></i> Tutup
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Buat modal jika belum ada
|
|
if (!$('#redamanDetailModal').length) {
|
|
$('body').append(`
|
|
<div class="modal fade" id="redamanDetailModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog modal-lg" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">
|
|
<i class="fas fa-info-circle"></i> Detail Redaman
|
|
</h4>
|
|
<button type="button" class="close" data-dismiss="modal">
|
|
<span>×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body" id="redamanDetailModalBody">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
$('#redamanDetailModalBody').html(modalHtml);
|
|
$('#redamanDetailModal').modal('show');
|
|
}
|
|
|
|
function showHistoriOnu(data, sn = '') {
|
|
|
|
let rows = '';
|
|
let labels = [];
|
|
let rxData = [];
|
|
|
|
data.forEach((item, index) => {
|
|
|
|
let rx = parseFloat(item.rx_power);
|
|
|
|
// ======================
|
|
// STATUS COLOR
|
|
// ======================
|
|
let statusColor = '';
|
|
let statusText = item.status;
|
|
|
|
if (item.status === 'LASER OUT') {
|
|
statusColor = 'red';
|
|
} else if (item.status === 'POWER FAIL') {
|
|
statusColor = 'orange';
|
|
} else {
|
|
statusColor = 'gray';
|
|
}
|
|
|
|
labels.push(item.created_at);
|
|
rxData.push(rx);
|
|
|
|
rows += `
|
|
<tr>
|
|
<td>${index + 1}</td>
|
|
<td>${item.created_at}</td>
|
|
|
|
<td style="color:${statusColor}; font-weight:bold;">
|
|
${item.status}
|
|
</td>
|
|
|
|
<td>${item.rx_power} dBm</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
let modalHtml = `
|
|
<div class="row">
|
|
|
|
<div class="col-md-12">
|
|
|
|
<!-- CHART -->
|
|
<div class="card mb-2">
|
|
<div class="card-header bg-dark text-white">
|
|
Histori Gangguan ONU - ${sn}
|
|
</div>
|
|
|
|
<div class="card-body" style="padding:10px;">
|
|
<canvas id="chartHistoriOnu" height="100"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TABLE -->
|
|
<div class="card">
|
|
|
|
<div class="card-header bg-danger text-white">
|
|
Histori Gangguan ONU
|
|
</div>
|
|
|
|
<div class="card-body table-responsive" style="max-height:400px; overflow:auto;">
|
|
|
|
<table class="table table-sm table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>No</th>
|
|
<th>Waktu</th>
|
|
<th>Status</th>
|
|
<th>RX Power</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${rows}
|
|
</tbody>
|
|
</table>
|
|
|
|
</div>
|
|
|
|
<div class="card-footer text-right">
|
|
<button class="btn btn-secondary btn-sm"
|
|
onclick="$('#historiOnuModal').modal('hide')">
|
|
Tutup
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
`;
|
|
|
|
// ======================
|
|
// MODAL INIT
|
|
// ======================
|
|
if (!$('#historiOnuModal').length) {
|
|
$('body').append(`
|
|
<div class="modal fade" id="historiOnuModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-body" id="historiOnuBody"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
$('#historiOnuBody').html(modalHtml);
|
|
$('#historiOnuModal').modal('show');
|
|
|
|
// ======================
|
|
// CHART
|
|
// ======================
|
|
setTimeout(() => {
|
|
|
|
if (window.historiOnuChart) {
|
|
window.historiOnuChart.destroy();
|
|
}
|
|
|
|
const ctx = document.getElementById('chartHistoriOnu');
|
|
|
|
window.historiOnuChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels.reverse(),
|
|
datasets: [
|
|
{
|
|
label: 'RX Power (dBm)',
|
|
data: rxData.reverse(),
|
|
borderColor: 'red',
|
|
borderWidth: 2,
|
|
pointRadius: 3,
|
|
fill: false
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
font: {
|
|
size: 10
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: false
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
}, 300);
|
|
}
|
|
|
|
function loadHistoriOnu(id) {
|
|
if (!id) {
|
|
console.warn('ID tidak valid:', id);
|
|
alert('ID tidak valid');
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: 'https://10.208.176.251/ftthplanner/api/histori_onu.php',
|
|
method: 'GET',
|
|
data: { id: id },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
|
|
if (response && response.success && response.data) {
|
|
showHistoriOnu(response.data, response.sn || '');
|
|
} else {
|
|
console.warn('Response tidak sesuai:', response);
|
|
alert('Data histori ONU tidak ditemukan');
|
|
}
|
|
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('AJAX Error:', error, xhr.responseText);
|
|
alert('Gagal memuat histori ONU');
|
|
}
|
|
});
|
|
}
|
|
|
|
function showHistoriRedaman(data, sn = '') {
|
|
|
|
let rows = '';
|
|
let labels = [];
|
|
let rxData = [];
|
|
let voltData = [];
|
|
|
|
data.forEach((item, index) => {
|
|
|
|
let rx = parseFloat(item.rx_power);
|
|
let volt = parseFloat(item.voltage);
|
|
|
|
// ======================
|
|
// 🔥 RX STATUS (FIX LOGIC)
|
|
// ======================
|
|
let rxStatus = '';
|
|
let rxColor = '';
|
|
|
|
if (rx < -25.99) {
|
|
rxStatus = 'TROUBLE (LOW REDAMAN)';
|
|
rxColor = 'red';
|
|
} else if (rx >= -19.99) {
|
|
rxStatus = 'OVER POWER (TOO STRONG)';
|
|
rxColor = 'orange';
|
|
} else {
|
|
rxStatus = 'NORMAL';
|
|
rxColor = 'green';
|
|
}
|
|
|
|
// ======================
|
|
// 🔥 VOLT STATUS
|
|
// ======================
|
|
let voltStatus = '';
|
|
let voltColor = '';
|
|
|
|
if (volt === null || isNaN(volt)) {
|
|
voltStatus = 'NO DATA';
|
|
voltColor = 'gray';
|
|
|
|
} else if (volt > 3.29) {
|
|
voltStatus = 'OVER VOLTAGE (TERLALU TINGGI)';
|
|
voltColor = 'red';
|
|
|
|
} else if (volt < 3.2) {
|
|
voltStatus = 'UNDER VOLTAGE (TERLALU RENDAH)';
|
|
voltColor = 'orange';
|
|
|
|
} else {
|
|
voltStatus = 'NORMAL (STABLE)';
|
|
voltColor = 'green';
|
|
}
|
|
|
|
labels.push(item.updated_at);
|
|
rxData.push(rx);
|
|
voltData.push(volt);
|
|
|
|
rows += `
|
|
<tr>
|
|
<td>${index + 1}</td>
|
|
<td>${item.updated_at}</td>
|
|
|
|
<td style="color:${rxColor}; font-weight:bold;">
|
|
${item.rx_power}
|
|
<br><small>${rxStatus}</small>
|
|
</td>
|
|
|
|
<td style="color:${voltColor}; font-weight:bold;">
|
|
${item.voltage}
|
|
<br><small>${voltStatus}</small>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
let modalHtml = `
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
|
|
<!-- CHART -->
|
|
<div class="card mb-2">
|
|
<div class="card-header bg-dark text-white">
|
|
Histori Redaman & Voltage - ${sn}
|
|
</div>
|
|
|
|
<div class="card-body" style="padding:10px;">
|
|
<canvas id="chartHistoriRedaman" height="80"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TABLE -->
|
|
<div class="card">
|
|
<div class="card-header bg-info text-white">
|
|
Histori Detail
|
|
</div>
|
|
|
|
<div class="card-body table-responsive" style="max-height:400px; overflow:auto;">
|
|
<table class="table table-sm table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>No</th>
|
|
<th>Waktu</th>
|
|
<th>RX Power</th>
|
|
<th>Voltage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${rows}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="card-footer text-right">
|
|
<button class="btn btn-secondary btn-sm"
|
|
onclick="$('#historiRedamanModal').modal('hide')">
|
|
Tutup
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// modal init
|
|
if (!$('#historiRedamanModal').length) {
|
|
$('body').append(`
|
|
<div class="modal fade" id="historiRedamanModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-body" id="historiRedamanBody"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
$('#historiRedamanBody').html(modalHtml);
|
|
$('#historiRedamanModal').modal('show');
|
|
|
|
// ======================
|
|
// 🔥 CHART
|
|
// ======================
|
|
setTimeout(() => {
|
|
|
|
if (window.historiChart) {
|
|
window.historiChart.destroy();
|
|
}
|
|
|
|
const ctx = document.getElementById('chartHistoriRedaman');
|
|
|
|
window.historiChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels.reverse(),
|
|
datasets: [
|
|
|
|
{
|
|
label: 'RX Power (dBm)',
|
|
data: rxData.reverse(),
|
|
borderColor: 'red',
|
|
borderWidth: 2,
|
|
pointRadius: 3,
|
|
fill: false
|
|
},
|
|
|
|
{
|
|
label: 'Voltage (V)',
|
|
data: voltData.reverse(),
|
|
borderColor: 'blue',
|
|
borderWidth: 2,
|
|
pointRadius: 3,
|
|
fill: false
|
|
},
|
|
|
|
// NORMAL ZONE LINE (-20 s/d -25)
|
|
{
|
|
label: 'Normal Zone (-20 s/d -25)',
|
|
data: labels.map(() => -22.5),
|
|
borderColor: 'green',
|
|
borderDash: [5, 5],
|
|
pointRadius: 0,
|
|
fill: false
|
|
},
|
|
|
|
// LIMIT LOSS LINE
|
|
{
|
|
label: 'Limit Loss (-25 dBm)',
|
|
data: labels.map(() => -25.99),
|
|
borderColor: 'red',
|
|
borderDash: [5, 5],
|
|
pointRadius: 0,
|
|
fill: false
|
|
}
|
|
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
font: { size: 10 }
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: false
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
}, 300);
|
|
|
|
}
|
|
// Contoh memanggil data dari API redaman
|
|
function loadRedamanDetail(id) {
|
|
if (!id) {
|
|
console.warn('ID tidak valid:', id);
|
|
alert('ID item tidak valid.');
|
|
return;
|
|
}
|
|
//redaman url bisa
|
|
$.ajax({
|
|
url: 'https://10.208.176.251/ftthplanner/api/detail_redaman.php', // path sesuai lokasi HTML/JS
|
|
method: 'GET',
|
|
data: { id: id }, // kirim 'id' sesuai PHP
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response && response.success && response.data) {
|
|
let data_rx_js = response
|
|
showRedamanDetail(response.data);
|
|
} else {
|
|
console.warn('Response tidak sesuai harapan:', response);
|
|
alert('Gagal memuat data redaman');
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('AJAX Error:', status, error, 'Response:', xhr.responseText);
|
|
alert('Terjadi kesalahan saat memuat data redaman');
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadHistoriRedaman(id) {
|
|
if (!id) {
|
|
console.warn('ID tidak valid:', id);
|
|
alert('ID tidak valid');
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: 'https://10.208.176.251/ftthplanner/api/histori_redaman.php',
|
|
method: 'GET',
|
|
data: { id: id },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
|
|
if (response && response.success && response.data) {
|
|
showHistoriRedaman(response.data);
|
|
} else {
|
|
console.warn('Response tidak sesuai:', response);
|
|
alert('Data histori tidak ditemukan');
|
|
}
|
|
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('AJAX Error:', error, xhr.responseText);
|
|
alert('Gagal memuat histori redaman');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Helper function to copy text to clipboard
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
showNotification('Koordinat disalin ke clipboard', 'success');
|
|
}).catch(function() {
|
|
showNotification('Gagal menyalin koordinat', 'error');
|
|
});
|
|
}
|
|
|
|
// Format date helper
|
|
function formatDate(dateString) {
|
|
if (!dateString) return '-';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString('id-ID', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
}
|
|
|
|
// Export functions to global scope
|
|
window.showAddItemModal = showAddItemModal;
|
|
window.addNewItem = addNewItem;
|
|
window.editItem = editItem;
|
|
window.editItemEnhanced = editItemEnhanced;
|
|
window.deleteItem = deleteItem;
|
|
window.showItemDetail = showItemDetail;
|
|
window.showItemList = showItemList;
|
|
window.showLaporanOdp = showLaporanOdp;
|
|
window.showRouteList = showRouteList;
|
|
window.focusOnItem = focusOnItem;
|
|
window.focusOnRoute = focusOnRoute;
|
|
window.deleteRoute = deleteRoute;
|
|
window.editRoute = editRoute;
|
|
window.calculateCoreAvailable = calculateCoreAvailable;
|
|
window.syncCoreUsageFromRoutes = syncCoreUsageFromRoutes;
|
|
window.copyToClipboard = copyToClipboard;
|
|
|
|
// Helper functions for display
|
|
function getCableTypeBadge(cableType) {
|
|
switch(cableType) {
|
|
case 'backbone': return 'danger';
|
|
case 'distribution': return 'primary';
|
|
case 'drop_core': return 'success';
|
|
case 'feeder': return 'info';
|
|
case 'branch': return 'warning';
|
|
default: return 'secondary';
|
|
}
|
|
}
|
|
|
|
function getCableTypeText(cableType) {
|
|
switch(cableType) {
|
|
case 'backbone': return 'Backbone';
|
|
case 'distribution': return 'Distribution';
|
|
case 'drop_core': return 'Drop Core';
|
|
case 'feeder': return 'Feeder';
|
|
case 'branch': return 'Branch';
|
|
default: return '-';
|
|
}
|
|
}
|
|
|
|
function getCoreUsageBadge(used, total) {
|
|
if (!used || !total) return 'secondary';
|
|
|
|
let percentage = (used / total) * 100;
|
|
if (percentage >= 90) return 'danger';
|
|
if (percentage >= 70) return 'warning';
|
|
if (percentage >= 50) return 'info';
|
|
return 'success';
|
|
}
|
|
|
|
// Export helper functions
|
|
window.getCableTypeBadge = getCableTypeBadge;
|
|
window.getCableTypeText = getCableTypeText;
|
|
window.getCoreUsageBadge = getCoreUsageBadge; |