v1.0.0
This commit is contained in:
@@ -1,358 +1,641 @@
|
||||
let socket, username;
|
||||
|
||||
let listEntrances = []
|
||||
let listApartment = []
|
||||
|
||||
let holdTimer;
|
||||
let startTime;
|
||||
let map_card;
|
||||
|
||||
const Card = {
|
||||
init: async (type, id) => {
|
||||
let html = await fetch('/lib/pages/card/index.html').then((response) => response.text());
|
||||
// Глобальні змінні стану
|
||||
id: null,
|
||||
socket: null,
|
||||
reconnectTimeout: null,
|
||||
reconnectAttempts: 0,
|
||||
username: null,
|
||||
listEntrances: [],
|
||||
listApartment: [],
|
||||
listBuilding: [],
|
||||
|
||||
// Кольори статусів квартир
|
||||
color_status: [
|
||||
["var(--ColorThemes2)", "var(--ColorThemes3)"],
|
||||
["#fbf1e0", "#ff8300"],
|
||||
["#fce3e2", "#ff0000"],
|
||||
["#d7ddec", "#2919bd"],
|
||||
["#d5e9dd", "#11a568"],
|
||||
["#d7ebfa", "#3fb4fc"],
|
||||
["#e8dbf5", "#b381eb"]
|
||||
],
|
||||
|
||||
// Ініціалізація сторінки
|
||||
async init(type, Id) {
|
||||
// Завантажуємо HTML
|
||||
const html = await fetch('/lib/pages/card/index.html').then(r => r.text());
|
||||
app.innerHTML = html;
|
||||
Card.id = Id;
|
||||
|
||||
house = id;
|
||||
// Закриваємо старий WebSocket
|
||||
if (this.socket) this.socket.close(1000, "Перезапуск з'єднання");
|
||||
this.cloud.start(makeid(6));
|
||||
|
||||
if (socket) socket.close(1000, "Перезапуск соединения");
|
||||
// Якщо це сторінка будинку, отримуємо під’їзди та стартуємо WebSocket
|
||||
if (type === "house") {
|
||||
const controls = document.getElementById('page-card-controls');
|
||||
controls.style.display = "flex";
|
||||
|
||||
if (type == "house") {
|
||||
getEntrances();
|
||||
start(makeid(6));
|
||||
// Застосовуємо режим сортування
|
||||
this.sort(localStorage.getItem('sort_mode'), false);
|
||||
this.getEntrances({ update: false });
|
||||
} else if (type === "homestead") {
|
||||
this.getHomestead.map({});
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", handleStart);
|
||||
document.addEventListener("touchstart", handleStart);
|
||||
document.addEventListener("mouseup", handleCancel);
|
||||
document.addEventListener("mouseleave", handleCancel);
|
||||
document.addEventListener("touchend", handleCancel);
|
||||
document.addEventListener("touchcancel", handleCancel);
|
||||
|
||||
function handleStart(event) {
|
||||
const button = event.target.closest(".hold-button");
|
||||
if (!button) return;
|
||||
|
||||
// event.preventDefault();
|
||||
startTime = Date.now();
|
||||
|
||||
holdTimer = setTimeout(() => {
|
||||
console.log("Долгое нажатие на", button.name);
|
||||
let number_id = button.name.split("-");
|
||||
mess(Number(number_id[0]), Number(number_id[1]));
|
||||
}, 1000);
|
||||
// Додаємо обробник закриття попапу
|
||||
const popup = document.getElementById('card-new-date');
|
||||
if (!popup.dataset.listenerAdded) {
|
||||
popup.addEventListener('click', (e) => {
|
||||
if (!popup.querySelector('.mess').contains(e.target)) {
|
||||
this.dateEditor.close();
|
||||
}
|
||||
});
|
||||
popup.dataset.listenerAdded = 'true';
|
||||
}
|
||||
},
|
||||
|
||||
function handleCancel() {
|
||||
const holdDuration = Date.now() - startTime; // Считаем, сколько длилось нажатие
|
||||
// Робота з WebSocket
|
||||
cloud: {
|
||||
start(name) {
|
||||
if (!name) return;
|
||||
Card.username = name;
|
||||
|
||||
if (holdDuration < 1000) {
|
||||
clearTimeout(holdTimer); // Если нажали менее 1 секунды, сбрасываем таймер
|
||||
const uuid = localStorage.getItem("uuid");
|
||||
const ws = new WebSocket(`${CONFIG.wss}?uuid=${uuid}`);
|
||||
Card.socket = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("[WebSocket] З'єднання встановлено");
|
||||
Card.cloud.setStatus('ok');
|
||||
ws.send(JSON.stringify({
|
||||
event: 'connection',
|
||||
id: getTimeInSeconds(),
|
||||
date: getTimeInSeconds(),
|
||||
uuid,
|
||||
username: name,
|
||||
data: {}
|
||||
}));
|
||||
Card.reconnectAttempts = 0;
|
||||
clearTimeout(Card.reconnectTimeout);
|
||||
};
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.event === 'connection' && data.username !== Card.username) {
|
||||
console.log(`Новий користувач: ${data.username}`);
|
||||
}
|
||||
if (data.event === 'message') {
|
||||
Card.cloud.update(data);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.warn("[WebSocket] З'єднання розірвано");
|
||||
Card.cloud.setStatus('err');
|
||||
Card.reconnectAttempts++;
|
||||
if (Card.reconnectAttempts <= 5) {
|
||||
Card.reconnectTimeout = setTimeout(() => {
|
||||
Card.getEntrances({ update: true });
|
||||
Card.cloud.start(Card.username);
|
||||
}, 1000);
|
||||
} else {
|
||||
if (confirm("З'єднання розірвано! Перепідключитись?")) {
|
||||
Card.reconnectAttempts = 0;
|
||||
Card.getEntrances({ update: true });
|
||||
Card.cloud.start(Card.username);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
console.error("[WebSocket] Помилка", err);
|
||||
Card.cloud.setStatus('err');
|
||||
};
|
||||
},
|
||||
|
||||
setStatus(mode) {
|
||||
const ids = ['cloud_1', 'cloud_2', 'cloud_3'];
|
||||
ids.forEach((id, idx) => {
|
||||
const el = document.getElementById(id);
|
||||
el.setAttribute('data-state', ['sync', 'ok', 'err'].indexOf(mode) === idx ? 'active' : '');
|
||||
});
|
||||
},
|
||||
|
||||
update(msg) {
|
||||
if (msg.type !== "apartment" && msg.type !== "building") return;
|
||||
|
||||
const [bg, color] = Card.color_status[msg.data.status];
|
||||
|
||||
const id = msg.data.id;
|
||||
const el = document.getElementById(`status_${id}`);
|
||||
const redDot = document.getElementById(`redDot_${id}`);
|
||||
|
||||
if (msg.type === "building") {
|
||||
redDot.style = `background:${bg};border:2px solid ${color}`;
|
||||
const apt = Card.listBuilding.find(e => e.id === id);
|
||||
|
||||
if (!apt) return;
|
||||
|
||||
apt.status = msg.data.status;
|
||||
apt.description = msg.data.description;
|
||||
apt.updated_at = msg.data.updated_at;
|
||||
} else if (msg.type === "apartment") {
|
||||
if (!el) return;
|
||||
|
||||
document.getElementById(`card_${id}`).style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
el.value = msg.data.status;
|
||||
el.style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
document.getElementById(`description_${id}`).value = msg.data.description;
|
||||
document.getElementById(`date_text_${id}`).innerText = formattedDateTime(msg.data.updated_at);
|
||||
}
|
||||
},
|
||||
|
||||
messApartment({ number, id, update, time }) {
|
||||
const apt = Card.listApartment[number]?.find(e => e.id === id);
|
||||
if (!apt) return;
|
||||
|
||||
const statusEl = document.getElementById(`status_${id}`);
|
||||
const descEl = document.getElementById(`description_${id}`);
|
||||
|
||||
let date = () => {
|
||||
if (!update && !time) {
|
||||
return apt.updated_at;
|
||||
} else if (update && !time) {
|
||||
return getTimeInSeconds();
|
||||
} else if (update && time) {
|
||||
return getTimeInSeconds(time);
|
||||
}
|
||||
}
|
||||
|
||||
apt.status = Number(statusEl.value);
|
||||
apt.description = descEl.value;
|
||||
apt.updated_at = date();
|
||||
|
||||
const [bg, color] = Card.color_status[apt.status];
|
||||
statusEl.style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
|
||||
const message = {
|
||||
event: 'message',
|
||||
id: getTimeInSeconds(),
|
||||
date: getTimeInSeconds(),
|
||||
username: Card.username,
|
||||
type: "apartment",
|
||||
data: {
|
||||
...apt,
|
||||
sheep_id: USER.id
|
||||
}
|
||||
};
|
||||
|
||||
if (Card.socket?.readyState === WebSocket.OPEN) {
|
||||
Card.socket.send(JSON.stringify(message));
|
||||
} else {
|
||||
if (confirm("З'єднання розірвано! Перепідключитись?")) {
|
||||
Card.getEntrances({ update: true });
|
||||
Card.cloud.start(Card.username);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
messBuildings({ id, update, time }) {
|
||||
const apt = Card.listBuilding.find(e => e.id === id);
|
||||
if (!apt) return;
|
||||
|
||||
const statusEl = document.getElementById(`status_${id}`);
|
||||
const descEl = document.getElementById(`description_${id}`);
|
||||
|
||||
let date = () => {
|
||||
if (!update && !time) {
|
||||
return apt.updated_at;
|
||||
} else if (update && !time) {
|
||||
return getTimeInSeconds();
|
||||
} else if (update && time) {
|
||||
return getTimeInSeconds(time);
|
||||
}
|
||||
}
|
||||
|
||||
apt.status = Number(statusEl.value);
|
||||
apt.description = descEl.value;
|
||||
apt.updated_at = date();
|
||||
|
||||
const [bg, color] = Card.color_status[apt.status];
|
||||
statusEl.style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
|
||||
const message = {
|
||||
event: 'message',
|
||||
id: getTimeInSeconds(),
|
||||
date: getTimeInSeconds(),
|
||||
username: Card.username,
|
||||
type: "building",
|
||||
data: {
|
||||
...apt,
|
||||
sheep_id: USER.id
|
||||
}
|
||||
};
|
||||
|
||||
if (Card.socket?.readyState === WebSocket.OPEN) {
|
||||
Card.socket.send(JSON.stringify(message));
|
||||
} else {
|
||||
if (confirm("З'єднання розірвано! Перепідключитись?")) {
|
||||
Card.getEntrances({ update: true });
|
||||
Card.cloud.start(Card.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// let color_status = [
|
||||
// "#000000",
|
||||
// "#C16917",
|
||||
// "#b10202",
|
||||
// "#3d3d3d",
|
||||
// "#11734b",
|
||||
// "#6cc5fc",
|
||||
// "#5a3286"
|
||||
// ];
|
||||
|
||||
// let color_status = [
|
||||
// ["#ffffff", "#000000"],
|
||||
// ["#e7af32", "#ffffff"],
|
||||
// ["#fc2a2a", "#ffffff"],
|
||||
// ["#3d3d3d", "#ffffff"],
|
||||
// ["#11a568", "#ffffff"],
|
||||
// ["#6cc5fc", "#ffffff"],
|
||||
// ["#b381eb", "#ffffff"]
|
||||
// ];
|
||||
|
||||
let color_status = [
|
||||
["var(--ColorThemes2)", "var(--ColorThemes3)"],
|
||||
["#fbf1e0", "#ff8300"],
|
||||
["#fce3e2", "#ff0000"],
|
||||
["#d7ddec", "#2919bd"],
|
||||
["#d5e9dd", "#11a568"],
|
||||
["#d7ebfa", "#3fb4fc"],
|
||||
["#e8dbf5", "#b381eb"]
|
||||
];
|
||||
|
||||
function start(name) {
|
||||
if (!name) return;
|
||||
|
||||
|
||||
document.getElementById("hash").innerText = `HASH: ${name}`
|
||||
username = name;
|
||||
|
||||
let uuid = localStorage.getItem("uuid");
|
||||
socket = new WebSocket(`${CONFIG.wss}?uuid=${uuid}`);
|
||||
|
||||
socket.onopen = function (e) {
|
||||
console.log("[WebSocket | open] Соединение установлено");
|
||||
document.getElementById("status").innerText = "WebSocket | open";
|
||||
|
||||
const message = {
|
||||
event: 'connection',
|
||||
id: getTimeInSeconds(),
|
||||
date: getTimeInSeconds(),
|
||||
uuid: uuid,
|
||||
username: name,
|
||||
data: {
|
||||
id: 1,
|
||||
entrance_id: 1,
|
||||
apartment_number: 1,
|
||||
title: "1",
|
||||
group_number: 1,
|
||||
status: 1,
|
||||
description: "",
|
||||
created_at: 1727541827,
|
||||
updated_at: 1727541827
|
||||
// Отримання під’їздів
|
||||
async getEntrances({ house_id = Card.id, update = false }) {
|
||||
const uuid = localStorage.getItem("uuid");
|
||||
const res = await fetch(`${CONFIG.api}/house/${house_id}/entrances`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": uuid
|
||||
}
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
this.listEntrances = data;
|
||||
|
||||
const container = document.getElementById('list');
|
||||
if (!update) container.innerHTML = "";
|
||||
|
||||
if (update) {
|
||||
for (const { id, entrance_number } of data) {
|
||||
this.getApartment({ id, number: entrance_number, update: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(message))
|
||||
};
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
socket.onmessage = function (event) {
|
||||
let data = JSON.parse(event.data)
|
||||
const canManage = USER.mode === 2 || (USER.mode === 1 && USER.possibilities.can_manager_territory);
|
||||
|
||||
if (data.event == 'connection') {
|
||||
if (data.username == username) return
|
||||
for (const element of data) {
|
||||
const { id, entrance_number, title, history, working } = element;
|
||||
const isMy = ((history.name === "Групова" && history.group_id == USER.group_id) || history.name === USER.name);
|
||||
|
||||
console.log(`Добавлен новый пользователь по имени ${data.username}`);
|
||||
} else if (data.event == 'message') {
|
||||
update(data);
|
||||
const show = (isMy && working) ? "open" : canManage ? "close" : null;
|
||||
if (!show) continue;
|
||||
|
||||
if (data.username == username) return
|
||||
const icon = isMy && working
|
||||
? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M 12 1 C 9.1277778 1 6.7189086 3.0461453 6.1230469 5.7871094 L 8.078125 6.2128906 C 8.4822632 4.3538547 10.072222 3 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>`
|
||||
: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M 12 1 C 8.6761905 1 6 3.6761905 6 7 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 8 8 L 8 7 C 8 4.7238095 9.7238095 3 12 3 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>`;
|
||||
|
||||
console.log(`${data.username} пишет: `, data.data);
|
||||
const details = document.createElement('details');
|
||||
if (show === "open") details.setAttribute('open', '');
|
||||
details.innerHTML = `
|
||||
<summary><p>${title}</p>${icon}</summary>
|
||||
<div id="apartments_${id}" class="apartments_list"></div>
|
||||
`;
|
||||
fragment.appendChild(details);
|
||||
|
||||
this.getApartment({ id, number: entrance_number, update: false });
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = function (event) {
|
||||
if (event.wasClean) {
|
||||
document.getElementById("status").innerText = "WebSocket | close"
|
||||
console.log(`[WebSocket | close] Соединение закрыто чисто, код=${event.code} причина=${event.reason}`);
|
||||
container.appendChild(fragment);
|
||||
},
|
||||
|
||||
async getApartment({ id, number, update }) {
|
||||
const uuid = localStorage.getItem('uuid');
|
||||
const res = await fetch(`${CONFIG.api}/apartment/${id}`, {
|
||||
headers: { "Authorization": uuid }
|
||||
});
|
||||
const data = await res.json();
|
||||
this.listApartment[number] = data;
|
||||
|
||||
const sort_mode = localStorage.getItem('sort_mode') ?? "1";
|
||||
const sorters = {
|
||||
"1": (a, b) => a.apartment_number - b.apartment_number,
|
||||
"2": (a, b) => b.apartment_number - a.apartment_number,
|
||||
"3": (a, b) => a.updated_at - b.updated_at,
|
||||
"4": (a, b) => b.updated_at - a.updated_at,
|
||||
};
|
||||
data.sort(sorters[sort_mode] || sorters["1"]);
|
||||
|
||||
const container = document.getElementById(`apartments_${id}`);
|
||||
if (!update) container.innerHTML = "";
|
||||
|
||||
const statusOptions = (selected) => {
|
||||
const labels = ["", "Відмова (Не цікавить)", "Не заходити (Груба відмова)", "Нема домофона", "Повторна відвідина", "Немає вдома", "Свідки Єгови"];
|
||||
return labels.map((txt, i) => `<option value="${i}" ${i === selected ? "selected" : ""}>${txt}</option>`).join("");
|
||||
};
|
||||
|
||||
if (update) {
|
||||
for (const apt of data) {
|
||||
const [bg, color] = this.color_status[apt.status];
|
||||
const style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
const dateEl = document.getElementById(`date_${apt.id}`);
|
||||
const cardEl = document.getElementById(`card_${apt.id}`);
|
||||
const statusEl = document.getElementById(`status_${apt.id}`);
|
||||
const dateTextEl = document.getElementById(`date_text_${apt.id}`);
|
||||
const descEl = document.getElementById(`description_${apt.id}`);
|
||||
|
||||
if (cardEl) cardEl.style = style;
|
||||
if (statusEl) {
|
||||
statusEl.value = apt.status;
|
||||
statusEl.style = style;
|
||||
}
|
||||
if (dateEl) dateEl.setAttribute('onclick', `Card.dateEditor.open({id: ${apt.id}, number: ${number}, updated_at: ${apt.updated_at}})`);
|
||||
if (dateTextEl) dateTextEl.innerText = formattedDateTime(apt.updated_at);
|
||||
if (descEl) descEl.innerText = apt.description ?? "";
|
||||
}
|
||||
} else {
|
||||
document.getElementById("status").innerText = "WebSocket | close"
|
||||
console.log('[WebSocket | close] Соединение прервано');
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
// setTimeout(function() {
|
||||
// start(username);
|
||||
// }, 1000);
|
||||
if (data.length == 0) {
|
||||
const p = document.createElement('p');
|
||||
|
||||
const result = confirm(`З'єднання розірвано! Перепідключитись?`);
|
||||
if (result) {
|
||||
getEntrances();
|
||||
start(username);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.onerror = function (error) {
|
||||
console.log(`[WebSocket | error]`);
|
||||
document.getElementById("status").innerText = "WebSocket | error"
|
||||
};
|
||||
}
|
||||
|
||||
function mess(entrance_number, id, date_type) {
|
||||
let sort_mode = localStorage.getItem('sort_mode') ?? true;
|
||||
console.log(id, listApartment[entrance_number]);
|
||||
|
||||
const pos = listApartment[entrance_number].map(e => e.id).indexOf(id);
|
||||
let apartment = listApartment[entrance_number][pos];
|
||||
|
||||
console.log(pos, apartment);
|
||||
|
||||
|
||||
let status = document.getElementById(`status_${id}`);
|
||||
let description = document.getElementById(`description_${id}`);
|
||||
let date = new Date(document.getElementById(`date_${id}`).value);
|
||||
const timestamp = date.getTime();
|
||||
|
||||
apartment.description = description.value;
|
||||
apartment.status = Number(status.value);
|
||||
apartment.updated_at = date_type ? getTimeInSeconds(timestamp) : getTimeInSeconds(),
|
||||
|
||||
|
||||
status.style.backgroundColor = color_status[status.value][0];
|
||||
status.style.color = color_status[status.value][1];
|
||||
status.style.border = `1px solid ${color_status[status.value][1]}`;
|
||||
|
||||
let user_hash = localStorage.getItem('hash');
|
||||
|
||||
let message = {
|
||||
event: 'message',
|
||||
id: getTimeInSeconds(),
|
||||
date: getTimeInSeconds(),
|
||||
hash: user_hash,
|
||||
username: username,
|
||||
data: {
|
||||
id: apartment.id,
|
||||
entrance_id: apartment.entrance_id,
|
||||
apartment_number: apartment.apartment_number,
|
||||
title: apartment.title,
|
||||
group_number: apartment.group_number,
|
||||
status: apartment.status,
|
||||
description: apartment.description,
|
||||
updated_at: apartment.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(message));
|
||||
|
||||
if (!date_type && sort_mode != 'false') sort(apartment.id, apartment.entrance_id);
|
||||
}
|
||||
|
||||
function update(message) {
|
||||
if (!document.getElementById(`status_${message.data.id}`)) return;
|
||||
|
||||
let now = new Date(message.data.updated_at);
|
||||
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
|
||||
|
||||
document.getElementById(`card_${message.data.id}`).style.backgroundColor = color_status[message.data.status][0];
|
||||
document.getElementById(`card_${message.data.id}`).style.color = color_status[message.data.status][1];
|
||||
document.getElementById(`card_${message.data.id}`).style.border = `1px solid ${color_status[message.data.status][1]}`;
|
||||
|
||||
document.getElementById(`status_${message.data.id}`).style.backgroundColor = color_status[message.data.status][0];
|
||||
document.getElementById(`status_${message.data.id}`).style.color = color_status[message.data.status][1];
|
||||
document.getElementById(`status_${message.data.id}`).style.border = `1px solid ${color_status[message.data.status][1]}`;
|
||||
|
||||
|
||||
|
||||
document.getElementById(`status_${message.data.id}`).value = message.data.status;
|
||||
document.getElementById(`description_${message.data.id}`).value = message.data.description;
|
||||
document.getElementById(`date_${message.data.id}`).value = now.toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
function getEntrances(house_id = house) {
|
||||
const uuid = localStorage.getItem('uuid');
|
||||
const URL = `${CONFIG.api}/house/${house_id}/entrances`;
|
||||
fetch(URL, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": uuid
|
||||
}
|
||||
})
|
||||
.then(function (response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
listEntrances = data;
|
||||
|
||||
document.getElementById('list').innerHTML = "";
|
||||
|
||||
for (let i = 0; i < listEntrances.length; i++) {
|
||||
const element = listEntrances[i];
|
||||
|
||||
let status = () => {
|
||||
if ((element.history.name == "Групова" || element.history.name == USER.name) && element.working) return "open";
|
||||
else if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_manager_territory)) return "close";
|
||||
|
||||
return "style='display: none;'"
|
||||
}
|
||||
|
||||
let statusIcon = () => {
|
||||
if ((element.history.name == "Групова" || element.history.name == USER.name) && element.working) return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M 12 1 C 9.1277778 1 6.7189086 3.0461453 6.1230469 5.7871094 L 8.078125 6.2128906 C 8.4822632 4.3538547 10.072222 3 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>'
|
||||
else return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M 12 1 C 8.6761905 1 6 3.6761905 6 7 L 6 8 C 4.9069372 8 4 8.9069372 4 10 L 4 20 C 4 21.093063 4.9069372 22 6 22 L 18 22 C 19.093063 22 20 21.093063 20 20 L 20 10 C 20 8.9069372 19.093063 8 18 8 L 18 7 C 18 3.6761905 15.32381 1 12 1 z M 12 3 C 14.27619 3 16 4.7238095 16 7 L 16 8 L 8 8 L 8 7 C 8 4.7238095 9.7238095 3 12 3 z M 6 10 L 18 10 L 18 20 L 6 20 L 6 10 z M 12 13 C 10.9 13 10 13.9 10 15 C 10 16.1 10.9 17 12 17 C 13.1 17 14 16.1 14 15 C 14 13.9 13.1 13 12 13 z"/></svg>'
|
||||
}
|
||||
|
||||
document.getElementById('list').innerHTML += `
|
||||
<details ${status()}>
|
||||
<summary>
|
||||
<p>${element.title}</p>
|
||||
${statusIcon()}
|
||||
</summary>
|
||||
<div id="apartments_${element.id}" class="apartments_list">
|
||||
|
||||
</div>
|
||||
</details>
|
||||
p.innerHTML = `
|
||||
Інформація про цей під'їзд відсутня. Надайте інформацію відповідальному за території.
|
||||
`;
|
||||
getApartment(element.id, element.entrance_number);
|
||||
|
||||
console.log(element);
|
||||
return container.appendChild(p);
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
for (const apt of data) {
|
||||
const [bg, color] = this.color_status[apt.status];
|
||||
const style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
|
||||
function getApartment(entrance_id, entrance_number) {
|
||||
const uuid = localStorage.getItem('uuid');
|
||||
const URL = `${CONFIG.api}/apartment/${entrance_id}`;
|
||||
fetch(URL, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": uuid
|
||||
}
|
||||
})
|
||||
.then(function (response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
listApartment[entrance_number] = data;
|
||||
const div = document.createElement('div');
|
||||
|
||||
data.sort((a, b) => a.apartment_number - b.apartment_number);
|
||||
div.className = `card_info`;
|
||||
div.id = `card_${apt.id}`;
|
||||
div.style = style;
|
||||
|
||||
data.sort((a, b) => a.updated_at - b.updated_at);
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const element = data[i];
|
||||
|
||||
let now = new Date(element.updated_at);
|
||||
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
|
||||
now = now.toISOString().slice(0, 16)
|
||||
|
||||
let disabled = () => {
|
||||
if (USER.administrator.uuid || (USER.moderator.uuid && USER.moderator.can_manager_territory)) return '';
|
||||
else if (element.status == 2) return "disabled";
|
||||
}
|
||||
|
||||
document.getElementById(`apartments_${entrance_id}`).innerHTML += `
|
||||
<div id="card_${element.id}" style="border: 1px solid ${color_status[element.status][1]};background: ${color_status[element.status][0]};color: ${color_status[element.status][1]};">
|
||||
<div class="info">
|
||||
<span>кв.${element.title}</span>
|
||||
<select id="status_${element.id}" onchange="mess(${entrance_number}, ${element.id})" style="background-color: ${color_status[element.status][0]}; color: ${color_status[element.status][1]}; border: 1px solid ${color_status[element.status][1]};" ${disabled()}>
|
||||
<option value="0" ${element.status == 0 ? "selected" : ""}></option>
|
||||
<option value="1" ${element.status == 1 ? "selected" : ""}>Відмова</option>
|
||||
<option value="2" ${element.status == 2 ? "selected" : ""}>Не заходити (Груба відмова)</option>
|
||||
<option value="3" ${element.status == 3 ? "selected" : ""}>Нема домофона</option>
|
||||
<option value="4" ${element.status == 4 ? "selected" : ""}>Повторна відвідина</option>
|
||||
<option value="5" ${element.status == 5 ? "selected" : ""}>Немає вдома</option>
|
||||
<option value="6" ${element.status == 6 ? "selected" : ""}>Свідки Єгови</option>
|
||||
</select>
|
||||
<input onchange="mess(${entrance_number}, ${element.id}, true)" name="${entrance_number}-${element.id}" class="hold-button" type="datetime-local" id="date_${element.id}" placeholder="Дата" value="${element.updated_at ? now : ""}" ${disabled()} style="max-width: 170px;">
|
||||
</div>
|
||||
<textarea onchange="mess(${entrance_number}, ${element.id}, true)" id="description_${element.id}" placeholder="Нотатки..." ${disabled()}}>${element.description ?? ""}</textarea>
|
||||
div.innerHTML = `
|
||||
<div class="info">
|
||||
<span>кв.${apt.title}</span>
|
||||
<select id="status_${apt.id}" onchange="Card.cloud.messApartment({number:${number},id:${apt.id},update:true})" style="${style}">
|
||||
${statusOptions(apt.status)}
|
||||
</select>
|
||||
<button id="date_${apt.id}" onclick="Card.dateEditor.open({id:${apt.id},number:${number},updated_at:${apt.updated_at}})">
|
||||
<p id="date_text_${apt.id}">${formattedDateTime(apt.updated_at)}</p>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
<textarea id="description_${apt.id}" onchange="Card.cloud.messApartment({number:${number},id:${apt.id}})" placeholder="Коротка нотатка.">${apt.description || ""}</textarea>
|
||||
`;
|
||||
|
||||
fragment.appendChild(div);
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
container.appendChild(fragment);
|
||||
}
|
||||
},
|
||||
|
||||
function sort(id, entrance_id) {
|
||||
let child = document.getElementById(`card_${id}`);
|
||||
getHomestead: {
|
||||
markers: {},
|
||||
|
||||
document.getElementById(`apartments_${entrance_id}`).removeChild(child);
|
||||
async loadAPI({ url }) {
|
||||
const uuid = localStorage.getItem("uuid");
|
||||
|
||||
document.getElementById(`apartments_${entrance_id}`).append(child);
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": uuid
|
||||
}
|
||||
});
|
||||
|
||||
child.style.border = "1px solid var(--PrimaryColor)";
|
||||
}
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
function getTimeInSeconds(time = Date.now()) {
|
||||
// Если время больше 10 знаков (это значит, что время в миллисекундах)
|
||||
if (time.toString().length < 10) {
|
||||
// Округляем до секунд, убирая последние 3 цифры (миллисекунды)
|
||||
time = Math.floor(time * 1000);
|
||||
async map({ homestead_id = Card.id }) {
|
||||
let data = await this.loadAPI({ url: `${CONFIG.api}homestead/${homestead_id}` });
|
||||
|
||||
console.log(data);
|
||||
|
||||
|
||||
let lat = data.geo?.lat ?? data.points?.[0]?.[0]?.[0]?.lat ?? 49.5629016;
|
||||
let lng = data.geo?.lng ?? data.points?.[0]?.[0]?.[0]?.lng ?? 25.6145625;
|
||||
let zoom = 15;
|
||||
|
||||
if (map_card && map_card.remove) {
|
||||
map_card.stopLocate();
|
||||
map_card.remove();
|
||||
}
|
||||
|
||||
const mapElement = document.getElementById('map_card');
|
||||
mapElement.style.display = "flex";
|
||||
if (!mapElement) return;
|
||||
|
||||
let googleHybrid = L.tileLayer('http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}', {
|
||||
maxZoom: 20,
|
||||
minZoom: 15,
|
||||
subdomains: ['mt0', 'mt1', 'mt2', 'mt3']
|
||||
});
|
||||
|
||||
let osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
});
|
||||
|
||||
let mytile = L.tileLayer('https://sheep-service.com/map/{z}/{x}/{y}.webp', {
|
||||
maxZoom: 20,
|
||||
minZoom: 15,
|
||||
tms: true
|
||||
});
|
||||
|
||||
map_card = L.map(mapElement, {
|
||||
renderer: L.canvas(),
|
||||
center: [lat, lng],
|
||||
zoom,
|
||||
zoomControl: false,
|
||||
layers: [
|
||||
googleHybrid,
|
||||
osm,
|
||||
mytile
|
||||
]
|
||||
});
|
||||
|
||||
// слежение в реальном времени
|
||||
map_card.locate({ setView: false, watch: true, enableHighAccuracy: true });
|
||||
map_card.on('locationfound', (e) => {
|
||||
if (!map_card._userMarker) {
|
||||
map_card._userMarker = L.marker(e.latlng).addTo(map_card)
|
||||
.bindPopup("Ви тут!");
|
||||
} else {
|
||||
map_card._userMarker.setLatLng(e.latlng);
|
||||
}
|
||||
});
|
||||
|
||||
let baseMaps = {
|
||||
"Google Hybrid": googleHybrid,
|
||||
"OpenStreetMap": osm,
|
||||
"Sheep Service Map": mytile,
|
||||
};
|
||||
|
||||
let layerControl = L.control.layers(baseMaps, [], { position: 'bottomright' }).addTo(map_card);
|
||||
|
||||
map_card.pm.setLang("ua");
|
||||
|
||||
const polygonOptions = {
|
||||
color: "#f2bd53",
|
||||
radius: 500,
|
||||
fillOpacity: 0.3,
|
||||
dashArray: '20,15',
|
||||
dashOffset: '20',
|
||||
};
|
||||
|
||||
L.polygon(data.points, polygonOptions).addTo(map_card);
|
||||
|
||||
map_card.setZoom(data.zoom);
|
||||
|
||||
// map_card.getZoom()
|
||||
|
||||
// console.log(data.zoom);
|
||||
|
||||
|
||||
Card.listBuilding = await this.loadAPI({ url: `${CONFIG.api}building/${homestead_id}` });
|
||||
|
||||
const statusOptions = (selected) => {
|
||||
const labels = ["", "Відмова (Не цікавить)", "Не заходити (Груба відмова)", "Нема домофона", "Повторна відвідина", "Немає вдома", "Свідки Єгови"];
|
||||
return labels.map((txt, i) => `<option value="${i}" ${i === selected ? "selected" : ""}>${txt}</option>`).join("");
|
||||
};
|
||||
|
||||
|
||||
// for (let i = 0; i < Card.listBuilding.length; i++) {
|
||||
// const element = Card.listBuilding[i];
|
||||
// const [bg, color] = Card.color_status[element.status];
|
||||
// const style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
|
||||
// const div = document.createElement('div');
|
||||
|
||||
// const redDot = L.divIcon({
|
||||
// className: "leaflet_drop",
|
||||
// html: `<div id="redDot_${element.id}" style='background:${bg};border:2px solid ${color}'></div>`,
|
||||
// iconSize: [16, 16],
|
||||
// iconAnchor: [8, 8]
|
||||
// });
|
||||
|
||||
// div.className = `card_info card_info_homestead`;
|
||||
// div.id = `card_${element.id}`;
|
||||
// div.style = style;
|
||||
|
||||
// div.innerHTML = `
|
||||
// <div class="info">
|
||||
// <select id="status_${element.id}" onchange="Card.cloud.messBuildings({id:${element.id},update:true})" style="${style}">
|
||||
// ${statusOptions(element.status)}
|
||||
// </select>
|
||||
// <button id="date_${element.id}" onclick="Card.dateEditor.open({id:${element.id},updated_at:${element.updated_at}})">
|
||||
// <p id="date_text_${element.id}">${formattedDateTime(element.updated_at)}</p>
|
||||
// <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>
|
||||
// </button>
|
||||
// </div>
|
||||
// <textarea id="description_${element.id}" onchange="Card.cloud.messBuildings({id:${element.id}})" placeholder="Коротка нотатка.">${element.description || ""}</textarea>
|
||||
// `;
|
||||
|
||||
// L.marker(element.geo, { icon: redDot }).addTo(map_card)
|
||||
// .bindPopup(div);
|
||||
// }
|
||||
|
||||
for (let i = 0; i < Card.listBuilding.length; i++) {
|
||||
const element = Card.listBuilding[i];
|
||||
const [bg, color] = Card.color_status[element.status];
|
||||
|
||||
const redDot = L.divIcon({
|
||||
className: "leaflet_drop",
|
||||
html: `<div id="redDot_${element.id}" style='background:${bg};border:2px solid ${color}'></div>`,
|
||||
iconSize: [16, 16],
|
||||
iconAnchor: [8, 8]
|
||||
});
|
||||
|
||||
// создаём маркер
|
||||
const marker = L.marker(element.geo, { icon: redDot }).addTo(map_card);
|
||||
|
||||
marker.bindPopup("");
|
||||
|
||||
// при открытии popup генерим div заново
|
||||
marker.on("popupopen", () => {
|
||||
const el = Card.listBuilding.find(e => e.id === element.id);
|
||||
const [bg, color] = Card.color_status[el.status];
|
||||
const style = `background:${bg};color:${color};border:1px solid ${color}`;
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.className = "card_info card_info_homestead";
|
||||
div.id = `card_${el.id}`;
|
||||
div.style = style;
|
||||
|
||||
let date = new Date(el.updated_at);
|
||||
date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="info">
|
||||
<select id="status_${element.id}" onchange="Card.cloud.messBuildings({id:${element.id},update:true})" style="${style}">
|
||||
${statusOptions(element.status)}
|
||||
</select>
|
||||
<input type="datetime-local" id="date_${element.id}" placeholder="Дата" onchange="Card.cloud.messBuildings({id:${element.id}})" value="${date.toISOString().slice(0, 16)}">
|
||||
</div>
|
||||
<textarea id="description_${element.id}" onchange="Card.cloud.messBuildings({id:${element.id}})" placeholder="Коротка нотатка.">${element.description || ""}</textarea>
|
||||
`;
|
||||
|
||||
marker.setPopupContent(div);
|
||||
});
|
||||
|
||||
Card.getHomestead.markers[element.id] = marker; // сохраним ссылку на маркер
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Сортування
|
||||
sort(mode, load) {
|
||||
const idx = Math.max(1, Math.min(4, Number(mode) || 1));
|
||||
['sort_1', 'sort_2', 'sort_3', 'sort_4'].forEach((id, i) => {
|
||||
document.getElementById(id)?.setAttribute('data-state', i + 1 === idx ? 'active' : '');
|
||||
});
|
||||
localStorage.setItem('sort_mode', idx);
|
||||
if (!load) this.getEntrances({ update: false });
|
||||
},
|
||||
|
||||
// Редактор дати
|
||||
dateEditor: {
|
||||
open({ id, number, updated_at }) {
|
||||
const block = document.getElementById('card-new-date');
|
||||
const input = document.getElementById('card-new-date-input');
|
||||
const button = document.getElementById('card-new-date-button');
|
||||
|
||||
// Приводимо дату до ISO без зсуву часового поясу
|
||||
let date = new Date(updated_at == 0 ? Date.now() : updated_at);
|
||||
date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
|
||||
input.value = date.toISOString().slice(0, 16)
|
||||
|
||||
// Призначаємо обробники
|
||||
input.setAttribute("onchange", `Card.dateEditor.edit({ id: ${id}, number: ${number} })`)
|
||||
button.setAttribute("onclick", `Card.dateEditor.edit({ id: ${id}, number: ${number}, type: 'now'})`)
|
||||
|
||||
|
||||
// Показуємо блок
|
||||
block.style.display = "";
|
||||
requestAnimationFrame(() => block.style.opacity = "1");
|
||||
},
|
||||
close() {
|
||||
// Робимо плавне зникнення
|
||||
const block = document.getElementById('card-new-date');
|
||||
block.style.opacity = "0";
|
||||
|
||||
const onTransitionEnd = () => {
|
||||
block.style.display = "none";
|
||||
block.removeEventListener("transitionend", onTransitionEnd);
|
||||
};
|
||||
block.addEventListener("transitionend", onTransitionEnd);
|
||||
},
|
||||
edit({ id, number, type }) {
|
||||
let input = document.getElementById('card-new-date-input').value;
|
||||
|
||||
if (type == "now") {
|
||||
Card.cloud.messApartment({ number: number, id: id, update: true });
|
||||
} else {
|
||||
if (input) {
|
||||
const ts = new Date(input).getTime();
|
||||
Card.cloud.messApartment({ number, id, update: true, time: ts });
|
||||
} else {
|
||||
Card.cloud.messApartment({ number: number, id: id });
|
||||
}
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
Reference in New Issue
Block a user