var PHARMACIES_DATA = [ {id:'А01',name:'Аптека №1 — Центральна', addr:'вул. Дерибасівська, 12', city:'Одеса', lat:46.4825,lng:30.7233,head:'1/1',pharm:'3/3',cashier:'2/3',schedule:'Пн–Нд 8–22',status:'ok',enterprise:'Одесская аптечная сеть'}, {id:'А07',name:'Аптека №7 — Черемушки', addr:'вул. Маршала Жукова, 54', city:'Одеса', lat:46.4191,lng:30.7058,head:'1/1',pharm:'1/2',cashier:'2/2',schedule:'Пн–Сб 9–20',status:'warn',enterprise:'Одесская аптечная сеть'}, {id:'А12',name:'Аптека №12 — Таїрова', addr:'вул. Академіка Корольова, 112',city:'Одеса', lat:46.4311,lng:30.7601,head:'1/1',pharm:'2/2',cashier:'1/1',schedule:'Пн–Нд 9–21',status:'ok',enterprise:'Одесская аптечная сеть'}, {id:'А23',name:'Аптека №23 — Котовський', addr:'пр. Добровольського, 133', city:'Одеса', lat:46.3952,lng:30.6487,head:'0/1',pharm:'1/2',cashier:'1/1',schedule:'Пн–Нд 8–20',status:'crit',enterprise:'Одесская аптечная сеть'}, {id:'А31',name:'Аптека №31 — Пересип', addr:'вул. Балківська, 74', city:'Одеса', lat:46.5089,lng:30.7412,head:'1/1',pharm:'2/2',cashier:'2/2',schedule:'Пн–Пт 9–18',status:'ok',enterprise:'Одесская аптечная сеть'}, {id:'А44',name:'Аптека №44 — Київ Центр', addr:'вул. Хрещатик, 15', city:'Київ', lat:50.4501,lng:30.5234,head:'1/1',pharm:'3/3',cashier:'2/3',schedule:'Пн–Нд 8–22',status:'ok',enterprise:'Киевская аптечная сеть'}, {id:'А51',name:'Аптека №51 — Лівобережна',addr:'пр. Броварський, 23', city:'Київ', lat:50.4721,lng:30.6115,head:'1/1',pharm:'1/2',cashier:'1/1',schedule:'Пн–Сб 8–21',status:'warn',enterprise:'Киевская аптечная сеть'}, {id:'А67',name:'Аптека №67 — Харків', addr:'пр. Науки, 44', city:'Харків',lat:49.9935,lng:36.2304,head:'0/1',pharm:'2/3',cashier:'1/2',schedule:'Пн–Нд 9–21',status:'crit',enterprise:'Харьковская аптечная сеть'}, ]; var PHARMA_SC = { ok: {label:'Норма', cls:'badge-success',color:'#22c55e',border:'#16a34a'}, warn:{label:'Увага', cls:'badge-warning',color:'#f59e0b',border:'#d97706'}, crit:{label:'Нестача',cls:'badge-danger', color:'#ef4444',border:'#dc2626'}, }; var pharmaState = { cur: 'table', lmap: null, markers: [], filtered: PHARMACIES_DATA.slice() }; function pharmaInit() { if (!document.getElementById('pharma-tbody')) return; pharmaState.filtered = PHARMACIES_DATA.slice(); pharmaRenderTable(pharmaState.filtered); // Предзагружаем Leaflet в фоне pharmaLoadLeaflet(function(){}); renderEnterpriseFilter(); } function pharmaLoadLeaflet(cb) { if (window.L) { cb(); return; } if (document.getElementById('leaflet-js')) { document.getElementById('leaflet-js').addEventListener('load', cb); return; } var lc = document.createElement('link'); lc.rel = 'stylesheet'; lc.href = 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css'; document.head.appendChild(lc); var ls = document.createElement('script'); ls.id = 'leaflet-js'; ls.src = 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js'; ls.onload = cb; document.head.appendChild(ls); } function pharmaScell(v) { var p = v.split('/'), c = +p[0], m = +p[1]; var col = c < m ? (c === 0 ? 'var(--color-destructive)' : 'var(--color-warning)') : 'var(--color-foreground)'; var fw = c < m ? '500' : '400'; return '' + v + ''; } function pharmaRenderTable(data) { var countEl = document.getElementById('pharma-count'); var tbody = document.getElementById('pharma-tbody'); if (!tbody) return; if (countEl) countEl.textContent = 'Показано ' + data.length + ' з ' + PHARMACIES_DATA.length + ' аптек'; tbody.innerHTML = data.map(function(p) { var c = PHARMA_SC[p.status]; return '' + '' + '
' + '
' + p.id + '
' + '
' + p.name + '
' + '
' + p.addr + '
' + '
' + '' + '' + p.city + '' + pharmaScell(p.head) + pharmaScell(p.pharm) + pharmaScell(p.cashier) + '' + p.schedule + '' + '' + c.label + '' + ''; }).join(''); } function pharmaMakeIcon(status) { var c = PHARMA_SC[status]; var svg = '' + '' + '' + 'Rx'; return L.divIcon({html:svg, iconSize:[28,36], iconAnchor:[14,36], popupAnchor:[0,-36], className:''}); } function pharmaRenderMarkers(data) { pharmaState.markers.forEach(function(m){ pharmaState.lmap.removeLayer(m); }); pharmaState.markers = []; data.forEach(function(p, i) { var c = PHARMA_SC[p.status]; var popup = '
' + '
' + p.name + '
' + '
' + p.addr + '
' + '' + '' + '' + '' + '' + '
Завідуючий' + p.head + '
Фармацевт' + p.pharm + '
Касир' + p.cashier + '
Режим' + p.schedule + '
' + c.label + '
'; var mk = L.marker([p.lat, p.lng], {icon: pharmaMakeIcon(p.status)}).addTo(pharmaState.lmap).bindPopup(popup); pharmaState.markers.push(mk); }); } function pharmaRenderSidebar(data) { var el = document.getElementById('pharma-sidebar'); if (!el) return; el.innerHTML = data.map(function(p, i) { var c = PHARMA_SC[p.status]; return '
' + '
' + '
' + '
' + p.id + '
' + '' + p.name.replace(/Аптека №\d+ — /, '') + '' + '
' + '' + c.label + '' + '
' + '
' + p.addr + '
' + '
'; }).join(''); } function pharmaFocus(i) { if (pharmaState.lmap && pharmaState.markers[i]) { pharmaState.lmap.setView(pharmaState.markers[i].getLatLng(), 15); pharmaState.markers[i].openPopup(); } } function pharmaAddBaseLayer(map) { if (typeof acoreAddMapBaseLayer === 'function') { acoreAddMapBaseLayer(map); return; } L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap', maxZoom: 19 }).addTo(map); } function pharmaInitMap() { requestAnimationFrame(function() { requestAnimationFrame(function() { var mapEl = document.getElementById('pharma-map'); if (!mapEl) return; if (!pharmaState.lmap) { pharmaState.lmap = L.map('pharma-map', {center:[48.3794,31.1656], zoom:6}); pharmaAddBaseLayer(pharmaState.lmap); } pharmaState.lmap.invalidateSize(); pharmaRenderMarkers(pharmaState.filtered); pharmaRenderSidebar(pharmaState.filtered); }); }); } function pharmaView(v) { pharmaState.cur = v; var isTable = v === 'table'; var pvTable = document.getElementById('pv-table'); var pvMap = document.getElementById('pv-map'); var btnT = document.getElementById('btn-table'); var btnM = document.getElementById('btn-map'); if (!pvTable) return; pvTable.style.display = isTable ? '' : 'none'; pvMap.style.display = isTable ? 'none' : ''; btnT.style.background = isTable ? 'var(--color-card)' : 'transparent'; btnT.style.color = isTable ? 'var(--color-foreground)' : 'var(--color-muted-foreground)'; btnM.style.background = isTable ? 'transparent' : 'var(--color-card)'; btnM.style.color = isTable ? 'var(--color-muted-foreground)' : 'var(--color-foreground)'; if (!isTable) { pharmaLoadLeaflet(pharmaInitMap); } } function pharmaFilter() { var s = (document.getElementById('pharma-search') || {}).value || ''; var city = (document.getElementById('filter-city') || {}).value || ''; var st = (document.getElementById('filter-status') || {}).value || ''; var enterprise = (document.getElementById('filter-enterprise') || {}).value || ''; s = s.toLowerCase(); pharmaState.filtered = PHARMACIES_DATA.filter(function(p) { return (!s || p.name.toLowerCase().indexOf(s) > -1 || p.addr.toLowerCase().indexOf(s) > -1) && (!city || p.city === city) && (!st || p.status === st) && (!enterprise || p.enterprise === enterprise); }); pharmaRenderTable(pharmaState.filtered); if (pharmaState.cur === 'map' && pharmaState.lmap) { pharmaRenderMarkers(pharmaState.filtered); pharmaRenderSidebar(pharmaState.filtered); } } function renderEnterpriseFilter() { var select = document.getElementById('filter-enterprise'); if (!select) return; var enterprises = PHARMACIES_DATA .map(function(p) { return p.enterprise; }) .filter(Boolean) .filter(function(value, index, array) { return array.indexOf(value) === index; }); select.innerHTML = '' + enterprises.map(function(e) { return ''; }).join(''); }