WEB_PROJECT_TA/assets/js/app.js

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>&times;</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>&times;</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>&times;</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>&times;</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;