Files
Rozenrod 6ec6523d71 Переработаны роутеры приложения
Переписано APi WebSocket для работы с новыми роутерами
2025-10-03 17:11:31 +03:00

1200 lines
57 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const Territory_editor = {
info: {},
// Ініціалізація редактора за типом об'єкта та його ID
async init(type, id) {
let html = await fetch('/lib/pages/territory/editor/index.html').then((response) => response.text());
app.innerHTML = html;
// Очищення груп та карти
map = "";
houseGroup = "";
homesteadGroup = "";
buildingGroup = "";
pointsGroup = "";
numApartments = 1;
this.info.id = Number(id);
this.info.type = type;
this.setHTML(type, id);
},
// Отримання даних з API з авторизацією через UUID
async loadAPI(URL) {
let uuid = localStorage.getItem("uuid");
return await fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
}).then((response) => response.json());
},
// Встановлення HTML-контенту для редактора залежно від типу об'єкта
async setHTML(type, id) {
let list = await Territory_editor.loadAPI(`${CONFIG.api}${type}/${id}`);
console.log(list);
// Заповнення даних об'єкта
let info_title = document.getElementById("info-title");
let info_number = document.getElementById("info-number");
let info_settlement = document.getElementById("info-settlement");
let info_osm = document.getElementById("info-osm");
this.info.points = list.points;
this.info.points_number = list.points_number ?? [];
this.info.geo = list.geo;
this.info.osm_id = list.osm_id;
this.info.zoom = list.zoom;
this.info.title = list.title;
this.info.number = list.number;
this.info.settlement = list.settlement;
this.info.description = list.description ?? null;
info_title.value = this.info.title;
info_number.value = this.info.number;
info_settlement.value = this.info.settlement;
info_osm.value = this.info.osm_id;
this.osm.init();
switch (type) {
case 'points':
this.points.init();
break;
case 'homestead':
this.homestead.init();
break;
case 'house':
this.house.init();
break;
}
},
points: {
init() { }
},
homestead: {
init() {
const part_3 = document.getElementById('part-3');
const title = part_3.querySelector('h1');
part_3.innerHTML = '';
title.innerHTML = `<span>Крок 3.</span> Створення будинків`;
part_3.appendChild(title);
part_3.innerHTML += `
<div class="info">
<p>*Натисніть кнопку нижче, а потім клацайте на карті, щоб додати будинки. Після цього натисніть "Зберегти".</p>
<p>*Щоб видалити будинок, клацніть на ньому та у спливаючому вікні оберіть "Видалити".</p>
<br />
<button onclick="Territory_editor.homestead.building.newHouse(this)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M 22.828125 3 C 22.316375 3 21.804562 3.1954375 21.414062 3.5859375 L 19 6 L 24 11 L 26.414062 8.5859375 C 27.195062 7.8049375 27.195062 6.5388125 26.414062 5.7578125 L 24.242188 3.5859375 C 23.851688 3.1954375 23.339875 3 22.828125 3 z M 17 8 L 5.2597656 19.740234 C 5.2597656 19.740234 6.1775313 19.658 6.5195312 20 C 6.8615312 20.342 6.58 22.58 7 23 C 7.42 23.42 9.6438906 23.124359 9.9628906 23.443359 C 10.281891 23.762359 10.259766 24.740234 10.259766 24.740234 L 22 13 L 17 8 z M 4 23 L 3.0566406 25.671875 A 1 1 0 0 0 3 26 A 1 1 0 0 0 4 27 A 1 1 0 0 0 4.328125 26.943359 A 1 1 0 0 0 4.3378906 26.939453 L 4.3632812 26.931641 A 1 1 0 0 0 4.3691406 26.927734 L 7 26 L 5.5 24.5 L 4 23 z"></path></svg>
<span>Додати будинок</span>
</button>
</div>
`;
part_3.style.display = "";
this.building.init();
},
building: {
list: [], editing: false,
async init() {
this.editing = false;
setLeafletCursor('pointer');
this.list = await Territory_editor.loadAPI(`${CONFIG.api}building/${Territory_editor.info.id}`);
// Обробник кліку на карту
homesteadGroup.on('click', e => {
console.log(this.editing);
if (e.layer instanceof L.Marker || !this.editing) return;
const { lat, lng } = e.latlng;
console.log(`Координати: ${lat.toFixed(5)}, ${lng.toFixed(5)}`);
setLeafletCursor('progress');
this.editing = false;
this.addBuilding({ geo: e.latlng, title: this.list.length + 1 });
});
for (const element of this.list) {
// Додаємо маркер на карту
const redDot = L.divIcon({
className: "leaflet_drop",
html: `<div id="redDot_${element.id}"></div>`,
iconSize: [16, 16],
iconAnchor: [8, 8]
});
L.marker(element.geo, { icon: redDot })
.addTo(buildingGroup)
.bindPopup(`
Точка: ${element.id}<br>
Координати: ${element.geo.lat.toFixed(5)}, ${element.geo.lng.toFixed(5)}<br>
<button class="map_dell" onclick="Territory_editor.homestead.building.delleteBuilding({id: ${element.id}})" type="button">Видалити</button>
`);
}
},
async addBuilding({ geo, title }) {
this.list.push({
title: title,
geo: geo
});
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}building/${Territory_editor.info.id}`;
try {
const response = await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({ title, geo })
});
const data = await response.json();
console.log(data);
// Додаємо маркер на карту
const redDot = L.divIcon({
className: "leaflet_drop",
html: `<div id="redDot_${data.id}"></div>`,
iconSize: [16, 16],
iconAnchor: [8, 8]
});
const marker = L.marker(geo, { icon: redDot }).addTo(buildingGroup);
marker.bindPopup(`
Точка: ${data.id}<br>
Координати: ${geo.lat.toFixed(5)}, ${geo.lng.toFixed(5)}<br>
<button class="map_dell" onclick="Territory_editor.homestead.building.delleteBuilding({id: ${data.id}})" type="button">Видалити</button>
`);
setLeafletCursor('crosshair');
this.editing = true;
} catch (err) {
console.error("Помилка при додаванні будівлі:", err);
}
},
async delleteBuilding({ id }) {
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}building/${id}`;
try {
const response = await fetch(URL, {
method: 'DELETE',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
});
const data = await response.json();
// Видаляємо елемент списку та маркер
const el = document.getElementById(`redDot_${id}`);
if (el) el.remove();
const block = document.getElementById(`Building_${id}`);
if (block) block.remove();
buildingGroup.eachLayer(layer => {
if (layer instanceof L.Marker && layer.getPopup()?.getContent().includes(`Точка: ${id}`)) {
buildingGroup.removeLayer(layer);
}
});
} catch (err) {
console.error("Помилка при видаленні будівлі:", err);
}
},
newHouse(element) {
const btn = element;
this.editing = !this.editing;
setLeafletCursor(this.editing ? 'crosshair' : 'pointer');
btn.innerHTML = this.editing
? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"> <path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z" ></path> </svg><span>Завершити додавання</span>`
: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"> <path d="M 22.828125 3 C 22.316375 3 21.804562 3.1954375 21.414062 3.5859375 L 19 6 L 24 11 L 26.414062 8.5859375 C 27.195062 7.8049375 27.195062 6.5388125 26.414062 5.7578125 L 24.242188 3.5859375 C 23.851688 3.1954375 23.339875 3 22.828125 3 z M 17 8 L 5.2597656 19.740234 C 5.2597656 19.740234 6.1775313 19.658 6.5195312 20 C 6.8615312 20.342 6.58 22.58 7 23 C 7.42 23.42 9.6438906 23.124359 9.9628906 23.443359 C 10.281891 23.762359 10.259766 24.740234 10.259766 24.740234 L 22 13 L 17 8 z M 4 23 L 3.0566406 25.671875 A 1 1 0 0 0 3 26 A 1 1 0 0 0 4 27 A 1 1 0 0 0 4.328125 26.943359 A 1 1 0 0 0 4.3378906 26.939453 L 4.3632812 26.931641 A 1 1 0 0 0 4.3691406 26.927734 L 7 26 L 5.5 24.5 L 4 23 z" ></path> </svg><span>Додати будинок</span>`;
if (this.editing) alert("Натискаючи на карту будуть створюватись нові точки (будинки)");
}
}
},
house: {
init() {
const part_3 = document.getElementById('part-3');
const title = part_3.querySelector('h1');
part_3.innerHTML = '';
title.innerHTML = `<span>Крок 3.</span> Конструктор квартир`;
part_3.appendChild(title);
part_3.innerHTML += `<input onchange="Territory_editor.house.apartments.editNum(this)" type="number" value="1" id="next-apartment-title" title="Авто-номер наступної квартири"><div id="house"></div>`;
part_3.style.display = "";
this.entrances.setHTML(Territory_editor.info.id);
},
entrances: {
list: [],
async setHTML(id) {
this.list = await Territory_editor.loadAPI(`${CONFIG.api}house/${id}/entrances`);
const houseDiv = document.getElementById('house');
if (!houseDiv) return;
houseDiv.innerHTML = "";
const newBtn = document.createElement('button');
newBtn.className = "entrance-button";
newBtn.type = "button";
newBtn.title = "Додати під'їзд";
newBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
newBtn.onclick = () => Territory_editor.house.entrances.addEntrance();
houseDiv.appendChild(newBtn);
for (const element of this.list) {
// Блок entrance
const entranceDiv = document.createElement('div');
entranceDiv.className = "entrance";
entranceDiv.id = `entrance-${element.id}`;
const input = document.createElement('input');
input.value = element.title;
input.type = "text"
input.onchange = () => Territory_editor.house.entrances.editEntrance(element.id, input.value);
const delBtn = document.createElement('button');
delBtn.type = "button";
delBtn.title = "Видалити під'їзд";
delBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
delBtn.onclick = () => Territory_editor.house.entrances.deleteEntrance(element.id);
const headerDiv = document.createElement('div');
headerDiv.className = "entrance-header";
headerDiv.append(input, delBtn);
const addBtn = document.createElement('button');
addBtn.className = "addFloors";
addBtn.type = "button";
addBtn.title = "Додати поверх";
addBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
addBtn.onclick = () => Territory_editor.house.apartments.addFloors(element.id);
const infoDiv = document.createElement('div');
infoDiv.className = "entrance-info";
infoDiv.append(headerDiv, addBtn);
entranceDiv.append(infoDiv);
houseDiv.insertBefore(entranceDiv, houseDiv.querySelector(".entrance-button"));
// Завантажуємо квартири для ентрансів
Territory_editor.house.apartments.setHTML(element.id);
}
},
async editEntrance(floor, value) {
const pos = this.list.findIndex(e => e.id === Number(floor));
if (pos === -1) return;
this.list[pos].title = value;
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}house/${Territory_editor.info.id}/entrances`;
try {
const response = await fetch(URL, {
method: 'PUT',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
id: this.list[pos].id,
title: this.list[pos].title,
description: this.list[pos].description
})
});
const data = await response.json();
console.log(data);
} catch (err) {
console.error("Помилка при редагуванні під'їзду:", err);
}
},
async addEntrance() {
console.log('addEntrance');
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}house/${Territory_editor.info.id}/entrances`;
try {
const response = await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
house_id: Territory_editor.info.id,
entrance_number: this.list.length,
title: `Під'їзд ${this.list.length + 1}`,
description: null
})
});
const data = await response.json();
console.log(data);
let element = {
id: data.id,
house_id: Territory_editor.info.id,
entrance_number: this.list.length,
title: `Під'їзд ${this.list.length + 1}`,
description: null
}
this.list.push(element);
Territory_editor.house.apartments.list[element.id] = []
console.log(this.list);
const houseDiv = document.getElementById('house');
const entranceDiv = document.createElement('div');
entranceDiv.className = "entrance";
entranceDiv.id = `entrance-${element.id}`;
const input = document.createElement('input');
input.value = element.title;
input.type = "text"
input.onchange = () => Territory_editor.house.entrances.editEntrance(element.id, input.value);
const delBtn = document.createElement('button');
delBtn.type = "button";
delBtn.title = "Видалити під'їзд";
delBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
delBtn.onclick = () => Territory_editor.house.entrances.deleteEntrance(element.id);
const headerDiv = document.createElement('div');
headerDiv.className = "entrance-header";
headerDiv.append(input, delBtn);
const addBtn = document.createElement('button');
addBtn.className = "addFloors";
addBtn.type = "button";
addBtn.title = "Додати поверх";
addBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
addBtn.onclick = () => Territory_editor.house.apartments.addFloors(element.id);
const infoDiv = document.createElement('div');
infoDiv.className = "entrance-info";
infoDiv.append(headerDiv, addBtn);
entranceDiv.append(infoDiv);
houseDiv.insertBefore(entranceDiv, houseDiv.querySelector(".entrance-button"));
} catch (err) {
console.error("Помилка при створенні під'їзду:", err);
}
},
async deleteEntrance(entrance) {
console.log(entrance);
if (Territory_editor.house.apartments.list[entrance].length == 0) {
console.log("OK");
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}house/${Territory_editor.info.id}/entrances`;
try {
const response = await fetch(URL, {
method: 'DELETE',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
id: entrance
})
});
const data = await response.json();
console.log(data);
const index = this.list.findIndex(item => item.id === entrance);
if (index !== -1) this.list.splice(index, 1);
delete Territory_editor.house.apartments.list[entrance];
document.getElementById(`entrance-${entrance}`).remove();
} catch (err) {
console.error("Помилка при видаленні під'їзду:", err);
}
} else {
alert("Для видалення під'їзду спочатку видаліть всі квартири з нього");
}
}
},
apartments: {
list: {},
async setHTML(id) {
this.list[id] = await Territory_editor.loadAPI(`${CONFIG.api}apartments/${id}`);
const entranceDiv = document.getElementById(`entrance-${id}`);
if (!entranceDiv) return;
// Унікальні поверхи
const uniqueFloors = [...new Set(this.list[id].map(a => a.floors_number))].sort((a, b) => a - b);
// Створюємо блоки поверхів
for (const num of uniqueFloors) {
const floorDiv = document.createElement('div');
floorDiv.className = "floor";
floorDiv.id = `floor-${id}-${num}`;
const h2 = document.createElement('h2');
h2.textContent = `Поверх ${num}`;
floorDiv.appendChild(h2);
const addBtn = document.createElement('button');
addBtn.id = `buttonApartment-${id}-${num}`;
addBtn.type = "button";
addBtn.title = "Додати квартиру";
addBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
addBtn.onclick = () => Territory_editor.house.apartments.addApartment(id, num);
const infoDiv = document.createElement('div');
infoDiv.className = "floor-info";
infoDiv.append(h2, addBtn);
floorDiv.appendChild(infoDiv);
entranceDiv.insertBefore(floorDiv, entranceDiv.querySelector(".floor"));
}
// Сортуємо квартири за назвою
this.list[id].sort((a, b) => b.title - a.title);
// Створюємо блоки квартир
for (const apartment of this.list[id]) {
const floorDiv = document.getElementById(`floor-${id}-${apartment.floors_number}`);
if (!floorDiv) continue;
const apartmentDiv = document.createElement('div');
apartmentDiv.className = "apartment";
apartmentDiv.id = `apartment-${id}-${apartment.id}`;
const input = document.createElement('input');
input.type = "text";
input.value = apartment.title;
input.id = `apartment-input-${id}-${apartment.id}`;
input.onchange = () => Territory_editor.house.apartments.editApartment(id, apartment.id);
const delBtn = document.createElement('button');
delBtn.type = "button";
delBtn.title = "Видалити квартиру";
delBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
delBtn.onclick = () => Territory_editor.house.apartments.deleteApartment(id, apartment.id);
apartmentDiv.append(input, delBtn);
floorDiv.prepend(apartmentDiv);
numApartments++;
}
const nextApartmentTitle = document.getElementById('next-apartment-title');
if (nextApartmentTitle) nextApartmentTitle.value = numApartments;
},
async addFloors(entrance) {
console.log(entrance);
const entranceBlock = document.getElementById(`entrance-${entrance}`);
const uniqueFloors = [...new Set(this.list[entrance].map(obj => obj.floors_number))];
console.log(uniqueFloors);
const newFloors = uniqueFloors.length + 1;
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${entrance}`;
try {
const response = await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
apartment_number: this.list[entrance].length,
title: numApartments.toString(),
floors_number: newFloors
})
});
const data = await response.json();
console.log(data);
// Створюємо блок поверху
const floorDiv = document.createElement('div');
floorDiv.className = "floor";
floorDiv.id = `floor-${entrance}-${newFloors}`;
// Заголовок поверху
const h2 = document.createElement('h2');
h2.textContent = `Поверх ${newFloors}`;
// Кнопка додати квартиру
const addBtn = document.createElement('button');
addBtn.className = "addApartment";
addBtn.id = `buttonApartment-${entrance}-${newFloors}`;
addBtn.title = "Додати квартиру";
addBtn.type = "button";
addBtn.onclick = () => Territory_editor.house.apartments.addApartment(entrance, newFloors);
addBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
const infoDiv = document.createElement('div');
infoDiv.className = "floor-info";
infoDiv.append(h2, addBtn);
// Блок квартири
const apartmentBlock = document.createElement('div');
apartmentBlock.className = "apartment";
apartmentBlock.id = `apartment-${entrance}-${data.id}`;
const input = document.createElement('input');
input.type = "text";
input.value = numApartments;
input.id = `apartment-input-${entrance}-${data.id}`;
input.onchange = () => Territory_editor.house.apartments.editApartment(entrance, data.id);
const delBtn = document.createElement('button');
delBtn.type = "button";
delBtn.title = "Видалити квартиру";
delBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
delBtn.onclick = () => Territory_editor.house.apartments.deleteApartment(entrance, data.id);
apartmentBlock.append(input, delBtn);
floorDiv.appendChild(apartmentBlock);
floorDiv.appendChild(infoDiv);
entranceBlock.insertBefore(floorDiv, entranceBlock.querySelector(".floor"));
// Оновлюємо список квартир
this.list[entrance].push({
id: data.id,
entrance_id: Number(entrance),
apartment_number: this.list[entrance].length,
title: numApartments.toString(),
floors_number: newFloors
});
numApartments++;
const nextApartmentTitle = document.getElementById('next-apartment-title');
if (nextApartmentTitle) nextApartmentTitle.value = numApartments;
} catch (err) {
console.error("Помилка при додаванні поверху:", err);
}
},
async addApartment(entrance, floor) {
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${entrance}`;
try {
const response = await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
apartment_number: this.list[entrance].length,
title: numApartments.toString(),
floors_number: Number(floor)
})
});
const data = await response.json();
console.log(data);
// Оновлюємо список квартир
this.list[entrance].push({
id: data.id,
entrance_id: Number(entrance),
apartment_number: this.list[entrance].length,
title: numApartments.toString(),
floors_number: Number(floor)
});
const floorDiv = document.getElementById(`floor-${entrance}-${floor}`);
// Створюємо блок нової квартири
const apartmentDiv = document.createElement('div');
apartmentDiv.className = "apartment";
apartmentDiv.id = `apartment-${entrance}-${data.id}`;
const input = document.createElement('input');
input.type = "text";
input.value = numApartments;
input.id = `apartment-input-${entrance}-${data.id}`;
input.onchange = () => Territory_editor.house.apartments.editApartment(entrance, data.id);
const delBtn = document.createElement('button');
delBtn.type = "button";
delBtn.title = "Видалити квартиру";
delBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path d="M 6.65625 4 C 6.367188 4 6.105469 4.113281 5.90625 4.3125 L 4.3125 5.90625 C 3.914063 6.304688 3.914063 7 4.3125 7.5 L 9.8125 13 L 4.3125 18.5 C 3.914063 19 3.914063 19.695313 4.3125 20.09375 L 5.90625 21.6875 C 6.40625 22.085938 7.101563 22.085938 7.5 21.6875 L 13 16.1875 L 18.5 21.6875 C 19 22.085938 19.695313 22.085938 20.09375 21.6875 L 21.6875 20.09375 C 22.085938 19.59375 22.085938 18.898438 21.6875 18.5 L 16.1875 13 L 21.6875 7.5 C 22.085938 7 22.085938 6.304688 21.6875 5.90625 L 20.09375 4.3125 C 19.59375 3.914063 18.898438 3.914063 18.5 4.3125 L 13 9.8125 L 7.5 4.3125 C 7.25 4.113281 6.945313 4 6.65625 4 Z"></path></svg>`;
delBtn.onclick = () => Territory_editor.house.apartments.deleteApartment(entrance, data.id);
apartmentDiv.append(input, delBtn);
floorDiv.insertBefore(apartmentDiv, floorDiv.querySelector(".floor-info"));
numApartments++;
const nextApartmentTitle = document.getElementById('next-apartment-title');
if (nextApartmentTitle) nextApartmentTitle.value = numApartments;
} catch (err) {
console.error("Помилка при додаванні квартири:", err);
}
},
async editApartment(entrance, apartment) {
console.log(entrance, apartment);
const input = document.getElementById(`apartment-input-${entrance}-${apartment}`);
if (!input) return;
const newTitle = input.value;
// Оновлюємо локальний список квартир
const pos = this.list[entrance].findIndex(e => e.id === Number(apartment));
if (pos === -1) return;
this.list[entrance][pos].title = newTitle;
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${entrance}`;
try {
const response = await fetch(URL, {
method: 'PUT',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({
id: this.list[entrance][pos].id,
title: this.list[entrance][pos].title,
status: this.list[entrance][pos].status,
description: this.list[entrance][pos].description
})
});
const data = await response.json();
console.log(data);
} catch (err) {
console.error("Помилка при редагуванні квартири:", err);
}
},
async deleteApartment(entrance, apartment) {
const pos = this.list[entrance].findIndex(e => e.id === Number(apartment));
if (pos === -1) return;
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartments/${entrance}`;
try {
const response = await fetch(URL, {
method: 'DELETE',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify({ id: this.list[entrance][pos].id })
});
const data = await response.json();
console.log(data);
// Видаляємо елемент з DOM
const apartmentBlock = document.getElementById(`apartment-${entrance}-${apartment}`);
if (apartmentBlock) apartmentBlock.remove();
// Оновлюємо локальний список
this.list[entrance].splice(pos, 1);
// Оновлюємо номер наступної квартири
numApartments = Math.max(0, numApartments - 1);
const nextApartmentTitle = document.getElementById('next-apartment-title');
if (nextApartmentTitle) nextApartmentTitle.value = numApartments;
} catch (err) {
console.error("Помилка при видаленні квартири:", err);
}
},
editNum(el) { numApartments = Number(el.value) },
},
},
osm: {
init() {
const center = Territory_editor.info.geo;
const zoom = Territory_editor.info.zoom;
const googleHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
});
const osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
const mytile = L.tileLayer('https://sheep-service.com/map/{z}/{x}/{y}.webp', {
maxZoom: 20, minZoom: 15, tms: true
});
if (!map) {
houseGroup = new L.FeatureGroup();
homesteadGroup = new L.FeatureGroup();
buildingGroup = new L.FeatureGroup();
pointsGroup = new L.FeatureGroup();
map = L.map('map', {
renderer: L.canvas(), center, zoom,
layers: [googleHybrid, osm, mytile, houseGroup, homesteadGroup, buildingGroup, pointsGroup],
zoomControl: false
});
L.control.layers(
{ "Google Hybrid": googleHybrid, "OpenStreetMap": osm, "Territory Map": mytile },
{
"Багатоповерхові будинки": houseGroup,
"Житлові райони": homesteadGroup,
"Приватні будинки": buildingGroup,
"Точки на карті": pointsGroup
},
{ position: 'bottomright' }
).addTo(map);
map.pm.addControls({
position: 'bottomright',
drawCircleMarker: false,
drawPolyline: false,
drawPolygon: false,
drawRectangle: false,
drawCircle: false,
drawText: false,
drawMarker: false,
cutPolygon: false,
tooltips: false,
editMode: true,
dragMode: true,
});
map.pm.toggleControls()
// Событие после завершения рисования
map.on('pm:create', e => {
const layer = e.layer;
let LatLngs = layer.getLatLngs();
LatLngs[0].push(LatLngs[0][0]);
Territory_editor.info.points.push(LatLngs);
let geo = this.center(layer.getLatLngs());
const house = layer; // сохраняем именно слой
if (Territory_editor.info.type === 'house') {
houseGroup.addLayer(house);
Territory_editor.info.points_number.push(this.center(layer.getLatLngs()));
} else if (Territory_editor.info.type === 'homestead') {
homesteadGroup.addLayer(house);
}
house.bindPopup(`
Координати: ${geo.lat.toFixed(5)}, ${geo.lng.toFixed(5)}<br>
<button class="map_dell" type="button">Видалити</button>
`);
// при открытии popup вешаем обработчик удаления
house.on('popupopen', (e) => {
if (Territory_editor.homestead.building.editing) {
house.closePopup();
return;
}
const btn = e.popup.getElement().querySelector('.map_dell');
if (btn) {
btn.addEventListener('click', () => {
Territory_editor.osm.delete(house);
});
}
});
Territory_editor.osm.autoZoom(Territory_editor.info.points);
});
map.pm.setLang("ua");
}
houseGroup.clearLayers();
homesteadGroup.clearLayers();
buildingGroup.clearLayers();
pointsGroup.clearLayers();
for (let i = 0; i < Territory_editor.info.points.length; i++) {
const LatLngs = Territory_editor.info.points[i];
// Создаем L.polygon
const polyOptions = Territory_editor.info.type === 'homestead'
? { color: "#f2bd53", fillColor: "#f2bd53", fillOpacity: 0.4, dashArray: '5,10' }
: { color: "#585858", fillColor: "#f2bd53", fillOpacity: 0.8 };
const house = L.polygon(LatLngs, polyOptions);
// Добавляем в нужную группу
if (Territory_editor.info.type === 'house') {
houseGroup.addLayer(house);
} else if (Territory_editor.info.type === 'homestead') {
homesteadGroup.addLayer(house);
}
house.bindPopup(`
Координати: ${Territory_editor.info.geo.lat.toFixed(5)}, ${Territory_editor.info.geo.lng.toFixed(5)}<br>
<button class="map_dell" type="button">Видалити</button>
`);
// при открытии popup вешаем обработчик удаления
house.on('popupopen', (e) => {
if (Territory_editor.homestead.building.editing) {
house.closePopup();
return;
}
const btn = e.popup.getElement().querySelector('.map_dell');
if (btn) {
btn.addEventListener('click', () => {
Territory_editor.osm.delete(house);
});
}
});
Territory_editor.osm.autoZoom(Territory_editor.info.points);
}
},
newPoligon() {
if (Territory_editor.info.type === 'house') {
map.pm.enableDraw('Polygon', {
snappable: true,
snapDistance: 20,
layerGroup: houseGroup,
templineStyle: {
color: '#585858',
radius: 500,
fillOpacity: 0.4,
dashArray: '5, 10',
dashOffset: '20',
},
hintlineStyle: {
color: '#C14D4D',
dashArray: '5, 10'
},
pathOptions: {
color: "#585858",
fillColor: "#f2bd53",
fillOpacity: 0.8
}
});
} else if (Territory_editor.info.type === 'homestead') {
map.pm.enableDraw('Polygon', {
snappable: true,
snapDistance: 20,
layerGroup: houseGroup,
templineStyle: {
color: '#585858',
radius: 500,
fillOpacity: 0.3,
dashArray: '5, 10',
dashOffset: '20',
},
hintlineStyle: {
color: '#C14D4D',
dashArray: '5, 10'
},
pathOptions: {
color: "#f2bd53",
fillColor: "#f2bd53",
radius: 500,
fillOpacity: 0.3,
dashArray: '5, 10'
}
});
}
},
async autoPoligon(IDs) {
if (!IDs) return;
const ids_list = IDs.replace(/\s+/g, "").split(',');
Territory_editor.info.osm_id = ids_list;
houseGroup.clearLayers();
homesteadGroup.clearLayers();
Territory_editor.info.points = [];
Territory_editor.info.points_number = [];
Territory_editor.info.geo = {}
// 1006306041, 1006306065
for (let i = 0; i < ids_list.length; i++) {
const element = await Territory_editor.osm.getOSM(Territory_editor.info.osm_id[i]);
// Преобразуем координаты в LatLng
const LatLngs = [[]];
element[0].forEach(feature => LatLngs[0].push({ lat: feature.lat, lng: feature.lng }));
// Замыкаем полигон
// if (LatLngs[0][0] && LatLngs[0][0] !== LatLngs[0][LatLngs[0].length - 1]) {
// LatLngs[0].push(LatLngs[0][0]);
// }
// Считаем центр
const center = this.center(LatLngs);
// Сохраняем в points / points_number
Territory_editor.info.points.push(LatLngs);
Territory_editor.info.points_number.push(center);
// Создаем L.polygon
const polyOptions = Territory_editor.info.type === 'homestead'
? { color: "#f2bd53", fillColor: "#f2bd53", fillOpacity: 0.4, dashArray: '5,10' }
: { color: "#585858", fillColor: "#f2bd53", fillOpacity: 0.8 };
const house = L.polygon(LatLngs, polyOptions);
// Добавляем в нужную группу
if (Territory_editor.info.type === 'house') {
houseGroup.addLayer(house);
} else if (Territory_editor.info.type === 'homestead') {
homesteadGroup.addLayer(house);
}
// Bind popup с кнопкой удаления
house.bindPopup(`
Координати: ${center.lat.toFixed(5)}, ${center.lng.toFixed(5)}<br>
<button class="map_dell" type="button">Видалити</button>
`);
house.on('popupopen', (e) => {
if (Territory_editor.homestead.building.editing) {
house.closePopup();
return;
}
const btn = e.popup.getElement().querySelector('.map_dell');
if (btn) {
btn.addEventListener('click', () => {
Territory_editor.osm.delete(house);
});
}
});
}
Territory_editor.osm.autoZoom(Territory_editor.info.points);
},
center(geo) {
// Получаем координаты полигона Leaflet
let latlngs = geo[0];
// Преобразуем в формат GeoJSON для Turf
const coordinates = latlngs.map(ll => [ll.lng, ll.lat]);
const polygonGeoJSON = {
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [coordinates]
}
};
// Находим центроид
const centroid = turf.centroid(polygonGeoJSON);
latlngs = { lat: centroid.geometry.coordinates[1], lng: centroid.geometry.coordinates[0] }
return latlngs;
},
autoZoom(polygons) {
if (!polygons || !polygons.length) return;
const allBounds = [];
polygons.forEach(polygon => {
const ring = polygon[0];
if (!ring || ring.length < 3) return;
const coords = ring.map(p => [p.lng, p.lat]);
if (coords[0][0] !== coords[coords.length - 1][0] || coords[0][1] !== coords[coords.length - 1][1]) {
coords.push(coords[0]);
}
const polygonGeoJSON = turf.polygon([coords]);
const bbox = turf.bbox(polygonGeoJSON);
const bounds = L.latLngBounds(
[bbox[1], bbox[0]],
[bbox[3], bbox[2]]
);
allBounds.push(bounds);
});
if (!allBounds.length) return;
// Если один полигон, просто fitBounds на него
if (allBounds.length === 1) {
map.fitBounds(allBounds[0]);
} else {
// Несколько полигонов → объединяем bounds
let finalBounds = allBounds[0];
for (let i = 1; i < allBounds.length; i++) {
finalBounds = finalBounds.extend(allBounds[i]);
}
map.fitBounds(finalBounds);
}
if (map.getZoom() > 18) map.setZoom(18);
setTimeout(() => {
Territory_editor.info.zoom = map.getZoom();
Territory_editor.info.geo = map.getCenter();
}, 200)
},
delete(house) {
// убрать слой с карты
if (Territory_editor.info.type === 'house') {
houseGroup.removeLayer(house);
} else if (Territory_editor.info.type === 'homestead') {
homesteadGroup.removeLayer(house);
}
// найти индекс полигона в points
const target_1 = house.getLatLngs(); // вершины полигона
const target_2 = house.getLatLngs();
target_1[0].push(target_1[0][0])
const index = Territory_editor.info.points.findIndex(
poly => {
if(JSON.stringify(poly[0]) === JSON.stringify(target_1[0])) return true
else if(JSON.stringify(poly[0]) === JSON.stringify(target_2[0])) return true
return false
}
);
console.log("index ", index);
console.log(Territory_editor.info.points);
if (index !== -1) {
// удалить из points и points_number по индексу
Territory_editor.info.points.splice(index, 1);
Territory_editor.info.points_number.splice(index, 1);
}
Territory_editor.osm.autoZoom(Territory_editor.info.points);
},
async getOSM(wayId) {
const overpassUrl = `https://overpass-api.de/api/interpreter?data=[out:json];way(${wayId});(._;>;);out;`;
return await fetch(overpassUrl)
.then(response => response.json())
.then(data => {
const nodes = new Map();
data.elements.forEach(el => {
if (el.type === "node") {
nodes.set(el.id, { lat: el.lat, lng: el.lon });
}
});
const way = data.elements.find(el => el.type === "way");
if (way) {
const coordinates = way.nodes.map(nodeId => nodes.get(nodeId));
return [coordinates];
} else {
console.error("Way не найден!");
}
})
.catch(error => console.error("Ошибка запроса:", error));
},
},
async save() {
console.log(Territory_editor.info);
setLeafletCursor('pointer');
Territory_editor.homestead.building.editing = false;
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}${Territory_editor.info.type}/${Territory_editor.info.id}`;
try {
const response = await fetch(URL, {
method: 'PUT',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
},
body: JSON.stringify(Territory_editor.info)
});
const data = await response.json();
console.log(data);
} catch (err) {
console.error("Помилка при редагуванні під'їзду:", err);
}
}
}