This commit is contained in:
2025-09-09 00:10:53 +03:00
parent 38f2a05107
commit 204fc092d7
239 changed files with 22447 additions and 9536 deletions

View File

@@ -0,0 +1,452 @@
const Card = {
socket: null,
reconnectTimeout: null,
reconnectNumber: 0,
username: null,
listEntrances: [],
listApartment: [],
color_status: [
["var(--ColorThemes2)", "var(--ColorThemes3)"],
["#fbf1e0", "#ff8300"],
["#fce3e2", "#ff0000"],
["#d7ddec", "#2919bd"],
["#d5e9dd", "#11a568"],
["#d7ebfa", "#3fb4fc"],
["#e8dbf5", "#b381eb"]
],
init: async (type, id) => {
let html = await fetch('/lib/pages/card/index.html').then((response) => response.text());
app.innerHTML = html;
house = id;
if (Card.socket) Card.socket.close(1000, "Перезапуск соединения");
Card.sort(localStorage.getItem('sort_mode'), false)
if (type == "house") {
Card.getEntrances({ update: false });
Card.cloud.start(makeid(6));
}
// Закриття вікно popup при натисканні за його межі
const block_card = document.getElementById('card-new-date');
const mess = block_card.querySelector('.mess');
if (!block_card.dataset.listenerAdded) {
block_card.addEventListener('click', function (event) {
if (!mess.contains(event.target)) {
Card.dateEditor.close();
}
});
block_card.dataset.listenerAdded = 'true';
}
},
cloud: {
status: (mode) => {
let cloud_1 = document.getElementById('cloud_1');
let cloud_2 = document.getElementById('cloud_2');
let cloud_3 = document.getElementById('cloud_3');
switch (mode) {
case 'sync':
cloud_1.setAttribute('data-state', 'active');
cloud_2.setAttribute('data-state', '');
cloud_3.setAttribute('data-state', '');
break;
case 'ok':
cloud_1.setAttribute('data-state', '');
cloud_2.setAttribute('data-state', 'active');
cloud_3.setAttribute('data-state', '');
break;
case 'err':
cloud_1.setAttribute('data-state', '');
cloud_2.setAttribute('data-state', '');
cloud_3.setAttribute('data-state', 'active');
break;
}
},
start: (name) => {
if (!name) return;
Card.username = name;
let uuid = localStorage.getItem("uuid");
Card.socket = new WebSocket(`${CONFIG.wss}?uuid=${uuid}`);
Card.socket.onopen = function (e) {
console.log("[WebSocket | open] З'єднання встановлено");
Card.cloud.status('ok');
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
}
}
Card.socket.send(JSON.stringify(message));
Card.reconnectNumber = 0;
clearTimeout(Card.reconnectTimeout);
};
Card.socket.onmessage = function (event) {
let data = JSON.parse(event.data)
if (data.event == 'connection') {
if (data.username == Card.username) return
console.log(`Доданий новий користувач на ім'я ${data.username}`);
} else if (data.event == 'message') {
Card.cloud.update(data);
if (data.username == Card.username) return
console.log(`${data.username} пише: `, data.data);
}
};
Card.socket.onclose = function (event) {
if (event.wasClean) {
console.log(`[WebSocket | close] З'єднання закрито чисто, код =${event.code} причина=${event.reason}`);
Card.cloud.status('err');
} else {
console.log(`[WebSocket | close] З'єднання перервано`);
Card.cloud.status('err');
Card.reconnectTimeout = setTimeout(function () {
Card.reconnectNumber++;
if (Card.reconnectNumber > 5) {
Card.reconnectNumber = 0;
clearTimeout(Card.reconnectTimeout);
const result = confirm(`З'єднання розірвано! Перепідключитись?`);
if (result) {
Card.getEntrances({ update: true });
Card.cloud.start(Card.username);
}
} else {
Card.getEntrances({ update: true });
Card.cloud.start(Card.username);
}
}, 500);
}
};
Card.socket.onerror = function (error) {
console.log(`[WebSocket | error]`);
Card.cloud.status('err');
};
},
mess: ({ number, id, update, time }) => {
const pos = Card.listApartment[number].map(e => e.id).indexOf(id);
let apartment = Card.listApartment[number][pos];
let status = document.getElementById(`status_${id}`);
let description = document.getElementById(`description_${id}`);
let date = () => {
if (!update && !time) {
return apartment.updated_at;
} else if (update && !time) {
return getTimeInSeconds();
} else if (update && time) {
return getTimeInSeconds(time);
}
}
apartment.description = description.value;
apartment.status = Number(status.value);
apartment.updated_at = date();
status.style.backgroundColor = Card.color_status[status.value][0];
status.style.color = Card.color_status[status.value][1];
status.style.border = `1px solid ${Card.color_status[status.value][1]}`;
let message = {
event: 'message',
id: getTimeInSeconds(),
date: getTimeInSeconds(),
username: Card.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,
sheep_id: USER.id
}
}
if (Card.socket && Card.socket.readyState === WebSocket.OPEN) {
Card.socket.send(JSON.stringify(message));
} else {
console.warn("WebSocket не підключено. Повідомлення не надіслано.");
const result = confirm(`З'єднання розірвано! Перепідключитись?`);
if (result) {
Card.getEntrances({ update: true });
Card.start(Card.username);
}
}
if (update) {
let sort_mode = localStorage.getItem('sort_mode') ?? '1';
if (sort_mode == '3') {
let child = document.getElementById(`card_${apartment.id}`);
document.getElementById(`apartments_${apartment.entrance_id}`).removeChild(child);
document.getElementById(`apartments_${apartment.entrance_id}`).append(child);
child.style.border = "1px solid var(--PrimaryColor)";
} else if (sort_mode == '4') {
let child = document.getElementById(`card_${apartment.id}`);
document.getElementById(`apartments_${apartment.entrance_id}`).removeChild(child);
document.getElementById(`apartments_${apartment.entrance_id}`).prepend(child);
child.style.border = "1px solid var(--PrimaryColor)";
}
}
},
update: (message) => {
if (!document.getElementById(`status_${message.data.id}`)) return;
document.getElementById(`card_${message.data.id}`).style.backgroundColor = Card.color_status[message.data.status][0];
document.getElementById(`card_${message.data.id}`).style.color = Card.color_status[message.data.status][1];
document.getElementById(`card_${message.data.id}`).style.border = `1px solid ${Card.color_status[message.data.status][1]}`;
document.getElementById(`status_${message.data.id}`).style.backgroundColor = Card.color_status[message.data.status][0];
document.getElementById(`status_${message.data.id}`).style.color = Card.color_status[message.data.status][1];
document.getElementById(`status_${message.data.id}`).style.border = `1px solid ${Card.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_text_${message.data.id}`).innerText = formattedDateTime(message.data.updated_at);
}
},
getEntrances: ({ house_id = house, update = false }) => {
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) {
Card.listEntrances = data;
const element_list = document.getElementById('list');
if (update) {
for (let i = 0; i < Card.listEntrances.length; i++) {
const element = Card.listEntrances[i];
Card.getApartment({ id: element.id, number: element.entrance_number, update: update });
}
} else {
element_list.innerHTML = "";
for (let i = 0; i < Card.listEntrances.length; i++) {
const element = Card.listEntrances[i];
let status = () => {
if ((element.history.name == "Групова" || element.history.name == USER.name) && element.working) return "open";
else if (USER.mode == 2 || (USER.mode == 1 && USER.possibilities.can_manager_territory)) return "close";
else 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>'
}
element_list.innerHTML += `
<details ${status()}>
<summary>
<p>${element.title}</p>
${statusIcon()}
</summary>
<div id="apartments_${element.id}" class="apartments_list">
</div>
</details>
`;
Card.getApartment({ id: element.id, number: element.entrance_number, update: false });
}
}
})
},
getApartment: ({ id, number, update }) => {
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}/apartment/${id}`;
fetch(URL, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
})
.then(function (response) {
return response.json();
})
.then(function (data) {
Card.listApartment[number] = data;
if (update) {
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)
document.getElementById(`card_${element.id}`).setAttribute('style', `border: 1px solid ${Card.color_status[element.status][1]};background: ${Card.color_status[element.status][0]};color: ${Card.color_status[element.status][1]};`);
document.getElementById(`status_${element.id}`).value = element.status;
document.getElementById(`status_${element.id}`).setAttribute('style', `background-color: ${Card.color_status[element.status][0]}; color: ${Card.color_status[element.status][1]}; border: 1px solid ${Card.color_status[element.status][1]};`);
document.getElementById(`date_${element.id}`).setAttribute('onclick', `Card.dateEditor.open({id: ${element.id}, number: ${number}, updated_at: ${element.updated_at}})`);
document.getElementById(`date_text_${element.id}`).innerText = element.updated_at ? formattedDateTime(element.updated_at) : "0.0.0000 00:00";
document.getElementById(`description_${element.id}`).innerText = element.description ?? "";
}
} else {
let sort_mode = localStorage.getItem('sort_mode') ?? 1;
if (sort_mode == "1")
data.sort((a, b) => a.apartment_number - b.apartment_number);
else if (sort_mode == "2")
data.sort((a, b) => b.apartment_number - a.apartment_number);
else if (sort_mode == "3")
data.sort((a, b) => a.updated_at - b.updated_at);
else if (sort_mode == "4")
data.sort((a, b) => b.updated_at - a.updated_at);
else
data.sort((a, b) => a.apartment_number - b.apartment_number);
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.possibilities.can_manager_territory) return '';
else if (element.status == 2) return "disabled";
}
document.getElementById(`apartments_${id}`).innerHTML += `
<div id="card_${element.id}" style="border: 1px solid ${Card.color_status[element.status][1]};background: ${Card.color_status[element.status][0]};color: ${Card.color_status[element.status][1]};">
<div class="info">
<span>кв.${element.title}</span>
<select id="status_${element.id}" onchange="Card.cloud.mess({ number: ${number}, id: ${element.id}, update: true})" style="background-color: ${Card.color_status[element.status][0]}; color: ${Card.color_status[element.status][1]}; border: 1px solid ${Card.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>
<button placeholder="Дата" id="date_${element.id}" onclick="Card.dateEditor.open({id: ${element.id}, number: ${number}, updated_at: ${element.updated_at}})" ${disabled()}>
<p id="date_text_${element.id}">${element.updated_at ? formattedDateTime(element.updated_at) : "0.0.0000 00:00"}</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 onchange="Card.cloud.mess({ number: ${number}, id: ${element.id}})" id="description_${element.id}" placeholder="Нотатки..." ${disabled()}}>${element.description ?? ""}</textarea>
</div>
`;
}
}
})
},
sort: (mode, load) => {
const sortIds = ['sort_1', 'sort_2', 'sort_3', 'sort_4'];
sortIds.forEach(id => {
const el = document.getElementById(id);
if (el) el.setAttribute('data-state', '');
});
let index = parseInt(mode, 10);
if (isNaN(index) || index < 1 || index > 4) index = 1;
const activeEl = document.getElementById(`sort_${index}`);
if (activeEl) activeEl.setAttribute('data-state', 'active');
localStorage.setItem('sort_mode', index.toString());
if (!load) Card.getEntrances({ update: false });
},
dateEditor: {
open: ({ id, number, updated_at }) => {
const block = document.getElementById('card-new-date');
const card_new_date_input = document.getElementById('card-new-date-input');
const card_new_date_button = document.getElementById('card-new-date-button');
let now = new Date(updated_at);
now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
now = now.toISOString().slice(0, 16)
card_new_date_input.value = now;
card_new_date_input.setAttribute("onchange", `Card.dateEditor.edit({ id: ${id}, number: ${number} })`)
card_new_date_button.setAttribute("onclick", `Card.dateEditor.edit({ id: ${id}, number: ${number}, type: 'now'})`)
block.style.display = "";
setTimeout(() => {
block.style.opacity = "1";
}, 100)
},
close: () => {
const block = document.getElementById('card-new-date');
block.style.opacity = "0";
setTimeout(() => {
block.style.display = "none";
}, 200)
},
edit: ({ id, number, type }) => {
const card_new_date_input = document.getElementById('card-new-date-input');
if (type == "now") {
Card.cloud.mess({ number: number, id: id, update: true });
} else {
if (card_new_date_input.value) {
let date = new Date(card_new_date_input.value);
const timestamp = date.getTime();
Card.cloud.mess({ number: number, id: id, update: true, time: timestamp });
} else {
Card.cloud.mess({ number: number, id: id });
}
}
Card.dateEditor.close();
}
}
}