897ffc99c9
Отдельное право на редактирование карточки аптеки, формы, пресеты колонок дислокации и правки видимости карточки. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
213 lines
12 KiB
JavaScript
213 lines
12 KiB
JavaScript
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 '<td style="padding:1rem;vertical-align:middle;color:' + col + ';font-weight:' + fw + ';">' + v + '</td>';
|
||
}
|
||
|
||
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 '<tr class="tr-hover" style="border-bottom:1px solid var(--color-border);cursor:pointer;transition:background 150ms;">' +
|
||
'<td style="padding:1rem;vertical-align:middle;">' +
|
||
'<div style="display:flex;align-items:center;gap:0.625rem;">' +
|
||
'<div style="width:2rem;height:2rem;border-radius:0.375rem;background:' + c.color + '22;color:' + c.border + ';display:flex;align-items:center;justify-content:center;font-size:0.625rem;font-weight:700;flex-shrink:0;">' + p.id + '</div>' +
|
||
'<div><div style="font-weight:500;color:var(--color-card-foreground);">' + p.name + '</div>' +
|
||
'<div style="font-size:0.75rem;color:var(--color-muted-foreground);">' + p.addr + '</div></div>' +
|
||
'</div>' +
|
||
'</td>' +
|
||
'<td style="padding:1rem;vertical-align:middle;color:var(--color-muted-foreground);">' + p.city + '</td>' +
|
||
pharmaScell(p.head) + pharmaScell(p.pharm) + pharmaScell(p.cashier) +
|
||
'<td style="padding:1rem;vertical-align:middle;color:var(--color-muted-foreground);font-size:0.8125rem;">' + p.schedule + '</td>' +
|
||
'<td style="padding:1rem;vertical-align:middle;"><span class="badge ' + c.cls + '">' + c.label + '</span></td>' +
|
||
'</tr>';
|
||
}).join('');
|
||
}
|
||
|
||
function pharmaMakeIcon(status) {
|
||
var c = PHARMA_SC[status];
|
||
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="36" viewBox="0 0 28 36">' +
|
||
'<path d="M14 0C6.268 0 0 6.268 0 14c0 9.333 14 22 14 22S28 23.333 28 14C28 6.268 21.732 0 14 0z" fill="' + c.color + '"/>' +
|
||
'<circle cx="14" cy="14" r="7" fill="white"/>' +
|
||
'<text x="14" y="18" text-anchor="middle" font-size="8" font-weight="700" fill="' + c.border + '" font-family="sans-serif">Rx</text></svg>';
|
||
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 = '<div style="font-family:sans-serif;min-width:190px;">' +
|
||
'<div style="font-weight:600;font-size:13px;margin-bottom:3px;">' + p.name + '</div>' +
|
||
'<div style="font-size:11px;color:#666;margin-bottom:8px;">' + p.addr + '</div>' +
|
||
'<table style="width:100%;font-size:11px;border-collapse:collapse;">' +
|
||
'<tr><td style="color:#888;padding:2px 0;">Завідуючий</td><td style="font-weight:500;text-align:right;">' + p.head + '</td></tr>' +
|
||
'<tr><td style="color:#888;padding:2px 0;">Фармацевт</td><td style="font-weight:500;text-align:right;">' + p.pharm + '</td></tr>' +
|
||
'<tr><td style="color:#888;padding:2px 0;">Касир</td><td style="font-weight:500;text-align:right;">' + p.cashier + '</td></tr>' +
|
||
'<tr><td style="color:#888;padding:2px 0;">Режим</td><td style="font-weight:500;text-align:right;">' + p.schedule + '</td></tr>' +
|
||
'</table><div style="margin-top:8px;padding:3px 8px;border-radius:20px;background:' + c.color + '22;color:' + c.border + ';font-size:11px;font-weight:600;display:inline-block;">' + c.label + '</div></div>';
|
||
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 '<div onclick="pharmaFocus(' + i + ')" class="li-hover" style="padding:0.75rem 1rem;border-bottom:1px solid var(--color-border);cursor:pointer;transition:background 150ms;">' +
|
||
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.2rem;">' +
|
||
'<div style="display:flex;align-items:center;gap:0.5rem;">' +
|
||
'<div style="width:1.75rem;height:1.75rem;border-radius:0.375rem;background:' + c.color + '22;color:' + c.border + ';display:flex;align-items:center;justify-content:center;font-size:0.5625rem;font-weight:700;flex-shrink:0;">' + p.id + '</div>' +
|
||
'<span style="font-size:0.8125rem;font-weight:500;color:var(--color-foreground);">' + p.name.replace(/Аптека №\d+ — /, '') + '</span>' +
|
||
'</div>' +
|
||
'<span class="badge ' + c.cls + '" style="font-size:0.625rem;flex-shrink:0;">' + c.label + '</span>' +
|
||
'</div>' +
|
||
'<div style="font-size:0.75rem;color:var(--color-muted-foreground);padding-left:2.25rem;">' + p.addr + '</div>' +
|
||
'</div>';
|
||
}).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: '© <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>',
|
||
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 = '<option value="">Все предприятия</option>' +
|
||
enterprises.map(function(e) {
|
||
return '<option value="' + e + '">' + e + '</option>';
|
||
}).join('');
|
||
}
|