// 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(''); // Populate core color dropdown let coreColorSelect = $('#coreColor'); coreColorSelect.empty().append(''); response.data.forEach(function(color) { let 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(''); odpSplitterSelect.empty().append(''); response.data.forEach(function(splitter) { let 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 = ` `; 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 += ` `; }); html += `
Nama ODP Total Client AVG RX Client Buruk Status Map Notif
${item.nama}
${item.analisa.split('|')[0]} ${rxText ? `
RX: ${rxText}` : ''} ${badClientText ? `
⚠ ${badClientText} ` : ''}
${item.total} ${item.avg_rx ?? '-'} dBm ${item.bad_count} ${item.status}
`; 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 = `
`; items.forEach(function(item) { html += ` `; }); html += `
Jenis Nama Alamat Koordinat Status Aksi
${item.item_type_name} ${item.name} ${item.address || '-'} ${(isNaN(parseFloat(item.latitude)) || isNaN(parseFloat(item.longitude))) ? 'Koordinat tidak valid' : `${parseFloat(item.latitude).toFixed(6)}, ${parseFloat(item.longitude).toFixed(6)}`} ${getStatusText(item.status)}
`; 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 = `
`; routes.forEach(function(route) { let distance = route.distance ? (route.distance / 1000).toFixed(2) + ' km' : '-'; html += ` `; }); html += `
Dari Ke Jarak Tipe Kabel Core Status Aksi
${route.from_item_name || 'Unknown'} ${route.to_item_name || 'Unknown'} ${distance} ${route.cable_type || '-'} ${route.core_count || '-'} ${getStatusText(route.status)}
`; 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(` `); } $('#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 = `
Jarak dihitung otomatis berdasarkan routing
`; // Create modal if doesn't exist if (!$('#routeEditModal').length) { $('body').append(` `); } $('#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 = `
${item.name}
Informasi Dasar
ID: ${item.id}
Jenis Item: ${item.item_type_name}
Nama: ${item.name}
Deskripsi: ${item.description || '-'}
Alamat: ${item.address || '-'}
Status: ${getStatusText(item.status)}
Informasi Lokasi
Latitude: ${item.latitude}
Longitude: ${item.longitude}
Koordinat: ${item.latitude}, ${item.longitude}
Google Maps: Buka di Maps

Informasi Core & Kabel
Warna Tube: ${item.tube_color_name ? ` ${item.tube_color_name} ` : '-'}
Warna Core: ${item.core_color_name ? ` ${item.core_color_name} ` : '-'}
Jenis Kabel: ${item.item_cable_type ? ` ${getCableTypeText(item.item_cable_type)} ` : '-'}
Kapasitas Core: ${item.total_core_capacity || 24} Core
Core Digunakan: ${item.core_used || 0} Core
Core Tersedia: ${(item.total_core_capacity || 24) - (item.core_used || 0)} Core
Informasi Splitter
Splitter Utama: ${item.splitter_main_ratio ? ` ${item.splitter_main_ratio} ` : '-'}
Splitter ODP: ${item.splitter_odp_ratio ? ` ${item.splitter_odp_ratio} ` : '-'}
Timestamp
Dibuat: ${formatDate(item.created_at)}
Diupdate: ${formatDate(item.updated_at)}
`; // Create modal if doesn't exist if (!$('#itemDetailModal').length) { $('body').append(` `); } $('#itemDetailModalBody').html(modalHtml); $('#itemDetailModal').modal('show'); } //detailredaman // Fungsi untuk menampilkan detail redaman function showRedamanDetail(item) { let modalHtml = `
Detail Redaman
Serial Number (SN): ${item.serial_number}
Rx Power: ${item.rx_power}
PPPoE IP: ${item.pppoe_ip && item.pppoe_ip !== 'Tidak tersedia' ? `${item.pppoe_ip}` : item.pppoe_ip}
`; // Buat modal jika belum ada if (!$('#redamanDetailModal').length) { $('body').append(` `); } $('#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 += ` ${index + 1} ${item.created_at} ${item.status} ${item.rx_power} dBm `; }); let modalHtml = `
Histori Gangguan ONU - ${sn}
Histori Gangguan ONU
${rows}
No Waktu Status RX Power
`; // ====================== // MODAL INIT // ====================== if (!$('#historiOnuModal').length) { $('body').append(` `); } $('#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 += ` ${index + 1} ${item.updated_at} ${item.rx_power}
${rxStatus} ${item.voltage}
${voltStatus} `; }); let modalHtml = `
Histori Redaman & Voltage - ${sn}
Histori Detail
${rows}
No Waktu RX Power Voltage
`; // modal init if (!$('#historiRedamanModal').length) { $('body').append(` `); } $('#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;