giyos1212's picture
Upload 72 files
98b6d67 verified
// static/dispatcher/script.js - TO'LIQ TAYYOR VERSIYA
// Brigade Tracking + Statistics + Clinic Map
// ==================== GLOBAL STATE ====================
let ws = null;
let reconnectInterval = null;
let currentFilter = null;
let clinicMap = null;
let clinicMarkers = [];
let markerClusterGroup = null;
let currentMapLayer = 'all';
let brigadeMarkers = {};
let brigadeUpdateInterval = null;
let casesHourlyChart = null;
let riskDistributionChart = null;
// ==================== INITIALIZATION ====================
document.addEventListener('DOMContentLoaded', function () {
console.log('πŸš€ Dispatcher panel ishga tushdi');
loadCases();
loadStatistics();
connectWebSocket();
initializeMap();
startBrigadeTracking();
initializeCharts();
setInterval(() => {
loadCases();
loadStatistics();
updateStatisticsCharts();
updateBrigadeStatistics();
}, 30000);
});
// ==================== WEBSOCKET ====================
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/api/ws/dispatcher`;
console.log('πŸ”Œ WebSocket ulanish...', wsUrl);
ws = new WebSocket(wsUrl);
ws.onopen = function () {
console.log('βœ… WebSocket ulandi');
if (reconnectInterval) {
clearInterval(reconnectInterval);
reconnectInterval = null;
}
};
ws.onmessage = function (event) {
try {
const data = JSON.parse(event.data);
console.log('πŸ“¨ WebSocket xabar:', data);
handleWebSocketMessage(data);
} catch (e) {
console.error('❌ WebSocket xabar parse qilishda xatolik:', e);
}
};
ws.onerror = function (error) {
console.error('❌ WebSocket xatolik:', error);
};
ws.onclose = function () {
console.log('πŸ”Œ WebSocket uzildi, qayta ulanish...');
if (!reconnectInterval) {
reconnectInterval = setInterval(() => {
connectWebSocket();
}, 5000);
}
};
}
function handleWebSocketMessage(data) {
const type = data.type;
switch (type) {
case 'new_case':
console.log('πŸ†• Yangi case:', data.case);
showNotification('Yangi murojat keldi!', 'bg-danger');
loadCases();
loadStatistics();
break;
case 'brigade_assigned':
console.log('πŸš‘ Brigada tayinlandi:', data.case);
showNotification('Brigada tayinlandi', 'bg-success');
loadCases();
break;
case 'name_received':
console.log('πŸ‘€ Ism qabul qilindi:', data.case);
loadCases();
break;
case 'operator_needed':
console.log('🎧 Operator kerak:', data.case);
showNotification('Operator kerak!', 'bg-warning');
loadCases();
break;
case 'clinic_recommended':
console.log('πŸ₯ Klinika tavsiya qilindi:', data.case);
showNotification('Klinikaga yo\'naltirildi', 'bg-info');
loadCases();
break;
default:
console.log('ℹ️ Noma\'lum xabar turi:', type);
}
}
// ==================== CASES MANAGEMENT ====================
async function loadCases() {
try {
const url = currentFilter ? `/api/cases?status=${currentFilter}` : '/api/cases';
const response = await fetch(url);
if (!response.ok) {
throw new Error('Cases yuklanmadi');
}
const cases = await response.json();
console.log(`πŸ“‹ ${cases.length} ta case yuklandi`);
renderCases(cases);
} catch (error) {
console.error('❌ Cases yuklashda xatolik:', error);
document.getElementById('cases-container').innerHTML = `
<div class="alert alert-danger m-3">
<i class="bi bi-exclamation-triangle-fill"></i>
Xatolik yuz berdi. Iltimos, sahifani yangilang.
</div>
`;
}
}
function renderCases(cases) {
const container = document.getElementById('cases-container');
if (cases.length === 0) {
container.innerHTML = `
<div class="text-center py-5">
<i class="bi bi-inbox" style="font-size: 4rem; color: #cbd5e0;"></i>
<p class="mt-3 text-muted">Hozircha murojatlar yo'q</p>
</div>
`;
return;
}
container.innerHTML = cases.map(c => renderCase(c)).join('');
}
function renderCase(c) {
const riskBadge = getRiskBadge(c.risk_level);
const typeBadge = getTypeBadge(c.type);
const statusBadge = getStatusBadge(c.status);
const timeAgo = getTimeAgo(c.created_at);
return `
<div class="case-card" onclick="viewCaseDetails('${c.id}')">
<div class="d-flex justify-content-between align-items-start mb-2">
<div class="flex-grow-1">
<h6 class="mb-1">
<i class="bi bi-person-circle me-1 text-primary"></i>
${c.patient_full_name || 'Bemor #' + c.id}
</h6>
<small class="text-muted">
<i class="bi bi-clock me-1"></i>${timeAgo}
</small>
</div>
<div class="text-end">
${riskBadge}
</div>
</div>
${c.symptoms_text ? `
<p class="mb-2 small text-muted">
<i class="bi bi-file-medical me-1"></i>
${c.symptoms_text.substring(0, 80)}${c.symptoms_text.length > 80 ? '...' : ''}
</p>
` : ''}
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
<div class="d-flex gap-1 flex-wrap">
${typeBadge}
${statusBadge}
${c.district ? `
<span class="badge bg-secondary">
<i class="bi bi-geo-alt-fill"></i> ${c.district}
</span>
` : ''}
</div>
<div class="d-flex gap-1 flex-wrap">
${c.assigned_brigade_name ? `
<span class="badge bg-primary">
<i class="bi bi-ambulance"></i> ${c.assigned_brigade_name}
</span>
` : ''}
${c.recommended_clinic_name ? `
<span class="badge bg-success">
<i class="bi bi-hospital"></i> ${c.recommended_clinic_name}
</span>
` : ''}
</div>
</div>
</div>
`;
}
function getRiskBadge(risk) {
if (risk === 'qizil') {
return '<span class="badge bg-danger badge-risk-qizil">πŸ”΄ QIZIL</span>';
} else if (risk === 'sariq') {
return '<span class="badge bg-warning text-dark badge-risk-sariq">🟑 SARIQ</span>';
} else if (risk === 'yashil') {
return '<span class="badge bg-success badge-risk-yashil">🟒 YASHIL</span>';
} else {
return '<span class="badge bg-secondary">βšͺ Noma\'lum</span>';
}
}
function getTypeBadge(type) {
if (type === 'emergency') {
return '<span class="badge bg-danger">πŸš‘ Tez yordam</span>';
} else if (type === 'public_clinic') {
return '<span class="badge bg-info">πŸ₯ Davlat</span>';
} else if (type === 'private_clinic') {
return '<span class="badge bg-success">πŸ₯ Xususiy</span>';
} else if (type === 'uncertain') {
return '<span class="badge bg-warning text-dark">❓ Noaniq</span>';
} else {
return '';
}
}
function getStatusBadge(status) {
const statusMap = {
'yangi': '<span class="badge bg-primary">Yangi</span>',
'qabul_qilindi': '<span class="badge bg-info">Qabul qilindi</span>',
'brigada_junatildi': '<span class="badge bg-warning text-dark">Brigada junatildi</span>',
'klinika_tavsiya_qilindi': '<span class="badge bg-success">Klinika tavsiya</span>',
'operator_kutilmoqda': '<span class="badge bg-danger">Operator kerak</span>',
'yopildi': '<span class="badge bg-secondary">Yopildi</span>'
};
return statusMap[status] || '<span class="badge bg-secondary">Noma\'lum</span>';
}
function getTimeAgo(timestamp) {
const now = new Date();
const created = new Date(timestamp);
const diffMs = now - created;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Hozir';
if (diffMins < 60) return `${diffMins} daqiqa oldin`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours} soat oldin`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays} kun oldin`;
}
function filterCases(risk) {
currentFilter = risk;
document.querySelectorAll('.btn-group button').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
loadCases();
}
function viewCaseDetails(caseId) {
console.log('View case details:', caseId);
alert(`Case details: ${caseId}\n(Bu funksiya keyinroq qo'shiladi)`);
}
// ==================== STATISTICS ====================
async function loadStatistics() {
try {
const response = await fetch('/api/cases');
if (!response.ok) {
throw new Error('Statistics yuklanmadi');
}
const cases = await response.json();
const emergency = cases.filter(c => c.type === 'emergency').length;
const uncertain = cases.filter(c => c.type === 'uncertain').length;
const clinic = cases.filter(c => c.type === 'public_clinic' || c.type === 'private_clinic').length;
const total = cases.length;
document.getElementById('stat-emergency').textContent = emergency;
document.getElementById('stat-uncertain').textContent = uncertain;
document.getElementById('stat-clinic').textContent = clinic;
document.getElementById('stat-total').textContent = total;
} catch (error) {
console.error('❌ Statistics yuklashda xatolik:', error);
}
}
// ==================== NOTIFICATIONS ====================
function showNotification(message, bgClass = 'bg-info') {
const toastHtml = `
<div class="toast align-items-center ${bgClass} text-white border-0" role="alert"
style="position: fixed; top: 80px; right: 20px; z-index: 9999;">
<div class="d-flex">
<div class="toast-body">
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto"
data-bs-dismiss="toast"></button>
</div>
</div>
`;
const toastElement = document.createElement('div');
toastElement.innerHTML = toastHtml;
document.body.appendChild(toastElement);
const toast = new bootstrap.Toast(toastElement.firstElementChild, {
delay: 3000
});
toast.show();
toastElement.firstElementChild.addEventListener('hidden.bs.toast', () => {
document.body.removeChild(toastElement);
});
}
// ==================== MAP FUNCTIONS ====================
function initializeMap() {
console.log('πŸ—ΊοΈ Xarita ishga tushmoqda...');
clinicMap = L.map('clinic-map', {
zoomControl: true,
scrollWheelZoom: true
}).setView([41.2995, 69.2401], 11);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> | CartoDB',
maxZoom: 19,
subdomains: 'abcd'
}).addTo(clinicMap);
markerClusterGroup = L.markerClusterGroup({
maxClusterRadius: 50,
spiderfyOnMaxZoom: true,
showCoverageOnHover: false,
zoomToBoundsOnClick: true,
iconCreateFunction: function (cluster) {
const count = cluster.getChildCount();
let size = 'small';
if (count > 10) size = 'large';
else if (count > 5) size = 'medium';
return L.divIcon({
html: `<div class="marker-cluster-custom marker-cluster-${size}">
<span>${count}</span>
</div>`,
className: 'marker-cluster-wrapper',
iconSize: L.point(40, 40)
});
}
});
clinicMap.addLayer(markerClusterGroup);
loadClinicsOnMap();
console.log('βœ… Xarita tayyor');
}
async function loadClinicsOnMap() {
try {
console.log('πŸ“ Klinikalarni yuklanmoqda...');
const response = await fetch('/api/clinics');
if (!response.ok) {
throw new Error('Klinikalar yuklanmadi');
}
const clinics = await response.json();
console.log(`πŸ“‹ ${clinics.length} ta klinika topildi`);
if (markerClusterGroup) {
markerClusterGroup.clearLayers();
}
clinicMarkers = [];
clinics.forEach(clinic => {
addClinicMarker(clinic);
});
} catch (error) {
console.error('❌ Klinikalarni yuklashda xatolik:', error);
}
}
function addClinicMarker(clinic) {
const lat = clinic.gps?.lat;
const lon = clinic.gps?.lon;
if (!lat || !lon) {
console.warn(`⚠️ GPS yo'q: ${clinic.name}`);
return;
}
const markerColor = clinic.type === 'davlat' ? '#3498db' : '#27ae60';
const iconClass = clinic.type === 'davlat' ? 'bi-hospital' : 'bi-hospital-fill';
const markerIcon = L.divIcon({
className: 'custom-clinic-marker',
html: `<div class="clinic-marker-icon" style="
background: ${markerColor};
width: 36px;
height: 36px;
border-radius: 50% 50% 50% 0;
border: 3px solid white;
box-shadow: 0 3px 10px rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
transform: rotate(-45deg);
position: relative;
">
<i class="bi ${iconClass}" style="transform: rotate(45deg);"></i>
</div>`,
iconSize: [36, 36],
iconAnchor: [18, 36],
popupAnchor: [0, -36]
});
const marker = L.marker([lat, lon], { icon: markerIcon });
const typeLabel = clinic.type === 'davlat' ?
'<span class="badge bg-info">Davlat</span>' :
'<span class="badge bg-success">Xususiy</span>';
const popupContent = `
<div class="clinic-popup">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="mb-0 flex-grow-1">${clinic.name}</h6>
${typeLabel}
</div>
<hr class="my-2">
<p class="mb-1 small text-muted">
<i class="bi bi-geo-alt-fill text-danger me-1"></i>
${clinic.district}
</p>
<p class="mb-1 small text-muted">
<i class="bi bi-telephone-fill text-primary me-1"></i>
${clinic.phone}
</p>
<p class="mb-2 small">
<i class="bi bi-star-fill text-warning me-1"></i>
<strong>${clinic.rating}</strong>/5.0
<span class="text-muted">(${clinic.doctors_count} doktor)</span>
</p>
<button class="btn btn-sm btn-primary w-100" onclick="showClinicDetails('${clinic.id}')">
<i class="bi bi-info-circle me-1"></i> Batafsil
</button>
</div>
`;
marker.bindPopup(popupContent, {
maxWidth: 250,
className: 'custom-popup'
});
marker.clinicType = clinic.type;
clinicMarkers.push(marker);
if (markerClusterGroup) {
markerClusterGroup.addLayer(marker);
}
}
function toggleMapLayer(layer) {
console.log(`πŸ”„ Layer o'zgartirildi: ${layer}`);
currentMapLayer = layer;
document.querySelectorAll('#btn-all, #btn-davlat, #btn-xususiy').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(`btn-${layer}`).classList.add('active');
if (markerClusterGroup) {
markerClusterGroup.clearLayers();
}
clinicMarkers.forEach(marker => {
if (layer === 'all') {
markerClusterGroup.addLayer(marker);
} else if (layer === 'davlat' && marker.clinicType === 'davlat') {
markerClusterGroup.addLayer(marker);
} else if (layer === 'xususiy' && marker.clinicType === 'xususiy') {
markerClusterGroup.addLayer(marker);
}
});
}
async function showClinicDetails(clinicId) {
try {
console.log(`πŸ“‹ Klinika ma'lumotlari yuklanmoqda: ${clinicId}`);
const response = await fetch(`/api/clinics/${clinicId}`);
if (!response.ok) {
throw new Error('Klinika topilmadi');
}
const clinic = await response.json();
document.getElementById('clinicModalTitle').innerHTML = `
<i class="bi bi-hospital-fill me-2"></i>${clinic.name}
`;
const modalBody = document.getElementById('clinicModalBody');
modalBody.innerHTML = `
${clinic.banner_url ? `
<img src="${clinic.banner_url}" class="img-fluid rounded mb-3" alt="${clinic.name}">
` : ''}
<div class="row mb-3">
<div class="col-md-6">
<p class="mb-2">
<strong>Turi:</strong>
<span class="badge ${clinic.type === 'davlat' ? 'bg-info' : 'bg-success'}">
${clinic.type === 'davlat' ? 'Davlat' : 'Xususiy'}
</span>
</p>
<p class="mb-2"><strong>Tuman:</strong> ${clinic.district}</p>
<p class="mb-2"><strong>Manzil:</strong> ${clinic.address}</p>
<p class="mb-2">
<strong>Telefon:</strong>
<a href="tel:${clinic.phone}">${clinic.phone}</a>
</p>
</div>
<div class="col-md-6">
<p class="mb-2">
<strong>Reyting:</strong>
<span class="text-warning">
${'β˜…'.repeat(Math.floor(clinic.rating))}${'β˜†'.repeat(5 - Math.floor(clinic.rating))}
</span>
${clinic.rating}/5.0
</p>
<p class="mb-2"><strong>Ish vaqti:</strong> ${clinic.working_hours}</p>
<p class="mb-2"><strong>Ish kunlari:</strong> ${clinic.working_days.join(', ')}</p>
<p class="mb-2"><strong>Doktorlar:</strong> ${clinic.doctors_count} ta</p>
</div>
</div>
${clinic.description ? `
<div class="alert alert-info mb-3">
<i class="bi bi-info-circle-fill me-1"></i> ${clinic.description}
</div>
` : ''}
<h6 class="mt-3 mb-2">Mutaxassisliklar</h6>
<div class="mb-3">
${clinic.specializations.map(spec => `
<span class="badge bg-primary me-1 mb-1">${spec}</span>
`).join('')}
</div>
${clinic.services && clinic.services.length > 0 ? `
<h6 class="mt-3 mb-2">Xizmatlar</h6>
<ul class="list-group mb-3">
${clinic.services.map(service => `
<li class="list-group-item d-flex justify-content-between align-items-center">
${service.name}
<span class="badge bg-success">${service.price}</span>
</li>
`).join('')}
</ul>
` : ''}
${clinic.doctors && clinic.doctors.length > 0 ? `
<h6 class="mt-3 mb-2">Doktorlar (${clinic.doctors.length})</h6>
<div class="row">
${clinic.doctors.slice(0, 6).map(doctor => `
<div class="col-md-6 mb-3">
<div class="card h-100">
<div class="card-body p-2">
<div class="d-flex align-items-center">
<img src="${doctor.photo_url}"
class="rounded-circle me-2"
style="width: 50px; height: 50px; object-fit: cover;"
alt="${doctor.full_name}">
<div>
<h6 class="mb-0 small">${doctor.full_name}</h6>
<p class="mb-0 text-muted" style="font-size: 0.75rem;">
${doctor.specialty}
</p>
<p class="mb-0" style="font-size: 0.7rem;">
<span class="text-warning">β˜…</span> ${doctor.rating}
</p>
</div>
</div>
</div>
</div>
</div>
`).join('')}
</div>
` : ''}
${clinic.photos && clinic.photos.length > 0 ? `
<h6 class="mt-3 mb-2">Fotogalereya</h6>
<div class="row">
${clinic.photos.map(photo => `
<div class="col-md-4 mb-2">
<img src="${photo.url}" class="img-fluid rounded" alt="${photo.caption}">
${photo.caption ? `<p class="text-center small text-muted mt-1">${photo.caption}</p>` : ''}
</div>
`).join('')}
</div>
` : ''}
`;
const modal = new bootstrap.Modal(document.getElementById('clinicModal'));
modal.show();
} catch (error) {
console.error('❌ Klinika ma\'lumotlarini yuklashda xatolik:', error);
alert('Klinika ma\'lumotlarini yuklashda xatolik yuz berdi');
}
}
// ==================== BRIGADE TRACKING ====================
function startBrigadeTracking() {
console.log('πŸš‘ Brigade tracking boshlandi');
updateBrigadeMarkers();
brigadeUpdateInterval = setInterval(() => {
updateBrigadeMarkers();
}, 3000);
}
async function updateBrigadeMarkers() {
try {
const response = await fetch('/api/brigades/live');
if (!response.ok) {
throw new Error('Brigadalar yuklanmadi');
}
const brigades = await response.json();
brigades.forEach(brigade => {
updateBrigadeMarker(brigade);
});
} catch (error) {
console.error('❌ Brigade tracking xatolik:', error);
}
}
function updateBrigadeMarker(brigade) {
const brigadeId = brigade.brigade_id;
const currentLat = brigade.current_lat;
const currentLon = brigade.current_lon;
if (!currentLat || !currentLon) return;
if (brigadeMarkers[brigadeId]) {
const marker = brigadeMarkers[brigadeId];
marker.setLatLng([currentLat, currentLon]);
marker.setPopupContent(getBrigadePopupContent(brigade));
} else {
const marker = createBrigadeMarker(brigade);
brigadeMarkers[brigadeId] = marker;
marker.addTo(clinicMap);
}
}
function createBrigadeMarker(brigade) {
const currentLat = brigade.current_lat;
const currentLon = brigade.current_lon;
const status = brigade.current_status;
const color = status === 'busy' ? '#dc3545' : '#28a745';
const icon = status === 'busy' ? 'πŸš‘' : '🟒';
const markerIcon = L.divIcon({
className: 'brigade-marker',
html: `<div class="brigade-marker-icon" style="
background: ${color};
width: 40px;
height: 40px;
border-radius: 50%;
border: 3px solid white;
box-shadow: 0 3px 12px rgba(0,0,0,0.4);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
animation: brigade-pulse 2s infinite;
">
${icon}
</div>`,
iconSize: [40, 40],
iconAnchor: [20, 20]
});
const marker = L.marker([currentLat, currentLon], {
icon: markerIcon,
zIndexOffset: 1000
});
marker.bindPopup(getBrigadePopupContent(brigade));
return marker;
}
function getBrigadePopupContent(brigade) {
const statusLabel = brigade.current_status === 'busy' ?
'<span class="badge bg-danger">Bandlik</span>' :
'<span class="badge bg-success">Bo\'sh</span>';
return `
<div class="brigade-popup">
<h6 class="mb-2">
<i class="bi bi-ambulance me-1"></i>
${brigade.name}
</h6>
<hr class="my-2">
<p class="mb-1 small">
<strong>Status:</strong> ${statusLabel}
</p>
<p class="mb-1 small text-muted">
<i class="bi bi-telephone-fill me-1"></i> ${brigade.phone}
</p>
<p class="mb-0 small text-muted">
<i class="bi bi-speedometer2 me-1"></i> ${brigade.speed_kmh} km/h
</p>
${brigade.assigned_case_id ? `
<p class="mb-0 small mt-2">
<span class="badge bg-primary">Case: ${brigade.assigned_case_id}</span>
</p>
` : ''}
</div>
`;
}
// ==================== STATISTICS CHARTS ====================
function initializeCharts() {
console.log('πŸ“Š Grafiklar ishga tushmoqda...');
const ctxHourly = document.getElementById('casesHourlyChart');
if (ctxHourly) {
casesHourlyChart = new Chart(ctxHourly, {
type: 'line',
data: {
labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
datasets: [{
label: 'Murojatlar',
data: [5, 3, 7, 12, 18, 15, 10, 8],
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 5
}
}
}
}
});
}
const ctxRisk = document.getElementById('riskDistributionChart');
if (ctxRisk) {
riskDistributionChart = new Chart(ctxRisk, {
type: 'doughnut',
data: {
labels: ['Qizil', 'Sariq', 'Yashil'],
datasets: [{
data: [0, 0, 0],
backgroundColor: [
'#dc3545',
'#ffc107',
'#28a745'
],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
updateStatisticsCharts();
}
async function updateStatisticsCharts() {
try {
const response = await fetch('/api/cases');
if (!response.ok) return;
const cases = await response.json();
const qizil = cases.filter(c => c.risk_level === 'qizil').length;
const sariq = cases.filter(c => c.risk_level === 'sariq').length;
const yashil = cases.filter(c => c.risk_level === 'yashil').length;
if (riskDistributionChart) {
riskDistributionChart.data.datasets[0].data = [qizil, sariq, yashil];
riskDistributionChart.update();
}
await updateBrigadeStatistics();
} catch (error) {
console.error('❌ Statistika yangilashda xatolik:', error);
}
}
async function updateBrigadeStatistics() {
try {
const response = await fetch('/api/brigades/live');
if (!response.ok) return;
const brigades = await response.json();
const busy = brigades.filter(b => b.current_status === 'busy').length;
const available = brigades.filter(b => b.current_status === 'available').length;
const total = brigades.length;
document.getElementById('active-brigades-count').textContent = total;
document.getElementById('busy-brigades').textContent = busy;
document.getElementById('available-brigades').textContent = available;
} catch (error) {
console.error('❌ Brigade statistika xatolik:', error);
}
}