Files
Rozenrod 85483b85bb Додан моніторінг застосунку
Додани веб компоненти карточок територій та повідомлень
2025-12-08 00:14:56 +02:00

445 lines
18 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 Stand_card = {
id: null,
async init(id) {
let html = await fetch('/lib/pages/stand/card/index.html').then((response) => response.text());
app.innerHTML = html;
Stand_card.id = id;
this.info.setHTML();
},
async loadAPI(url) {
const uuid = localStorage.getItem("uuid");
const response = await fetch(url, {
method: 'GET',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
});
return await response.json();
},
info: {
list: [],
sheeps: [],
async setHTML() {
this.list = await Stand_card.loadAPI(`${CONFIG.api}stand/${Stand_card.id}`);
this.sheeps = await Stand_card.loadAPI(`${CONFIG.api}sheeps/list/stand`);
document.getElementById('stand-info-title').innerText = this.list.title;
if(this.list.geo[0]>0){
document.getElementById('stand-info-geo').innerHTML = 'Відкрити Google Maps';
document.getElementById('stand-info-geo').href = `https://www.google.com/maps?q=${this.list.geo[0]},${this.list.geo[1]}`;
document.getElementById('stand-info-block-geo').style.display = "";
} else {
document.getElementById('stand-info-block-geo').style.display = "none";
}
document.getElementById('stand-info-image').setAttribute('src', '');
Stand_card.schedule.setHTML();
}
},
// Робота з WebSocket
cloud: {
update(msg) {
const { type, data, user } = msg;
const el = document.getElementById(`name-${data?.id}`);
if (!el) return; // якщо елемент не знайдено - виходимо
const isSelf = user.id == USER.id;
switch (type) {
case "stand_locking":
if (!isSelf) {
el.disabled = true;
el.style.border = "2px solid var(--PrimaryColor)";
el.style.backgroundColor = "#EAFF0024";
}
break;
case "stand_unlocking":
// Розблокуємо лише якщо подія від іншого користувача
if (!isSelf) {
el.style.border = "";
el.style.backgroundColor = "";
}
if ((!isSelf && !el.value) || USER.possibilities.can_manager_stand) {
el.removeAttribute("disabled");
}
break;
case "stand_update": {
const sid = data.sheep_id;
const sname = data.sheep_name ?? "";
// Менеджеру показуємо весь перелік
if (USER.possibilities.can_manager_stand && Array.isArray(Sheeps?.sheeps_list?.list)) {
el.innerHTML = "";
// порожній варіант
el.appendChild(Object.assign(document.createElement("option"), {
value: "",
textContent: " "
}));
// заповнюємо всіх овечок
Stand_card.info.sheeps.forEach(s => {
const opt = document.createElement("option");
opt.value = s.id;
opt.textContent = s.name;
if (s.id == sid) opt.selected = true;
el.appendChild(opt);
});
el.removeAttribute("disabled");
} else {
// Звичайна поведінка для звичайних користувачів
if (sid == USER.id) {
el.innerHTML = `<option value=""></option><option selected value="${USER.id}">${USER.name}</option>`;
el.removeAttribute("disabled");
} else if (!sid) {
el.innerHTML = `<option value=""></option><option value="${USER.id}">${USER.name}</option>`;
el.removeAttribute("disabled");
} else {
el.innerHTML = `<option value="${sid}">${sname}</option>`;
el.disabled = true;
}
}
el.style.border = "";
el.style.backgroundColor = "";
break;
}
default:
return;
}
},
mess: {
locking({ id }) {
const message = {
event: 'message',
user: {
name: USER.name,
id: USER.id
},
type: "stand_locking",
data: {
id: id,
stand_id: Stand_card.id,
sheep_name: null
}
};
if (navigator.onLine && Cloud.socket?.readyState === WebSocket.OPEN) {
Cloud.socket.send(JSON.stringify(message));
} else {
if (confirm("З'єднання розірвано! Перепідключитись?")) {
Cloud.start();
}
}
},
unlocking({ id }) {
const message = {
event: 'message',
user: {
name: USER.name,
id: USER.id
},
type: "stand_unlocking",
data: {
id: id,
stand_id: Stand_card.id
}
};
if (navigator.onLine && Cloud.socket?.readyState === WebSocket.OPEN) {
Cloud.socket.send(JSON.stringify(message));
} else {
if (confirm("З'єднання розірвано! Перепідключитись?")) {
Cloud.start();
}
}
},
update({ sheep_id, id }) {
const message = {
event: 'message',
user: {
name: USER.name,
id: USER.id
},
type: "stand_update",
data: {
id: id,
stand_id: Stand_card.id,
sheep_id: sheep_id,
sheep_name: null
}
};
if (USER.possibilities.can_manager_stand) {
const pos = Stand_card.info.sheeps.map(e => e.id).indexOf(Number(sheep_id));
if (pos != -1) {
let name = Stand_card.info.sheeps[pos].name;
message.data.sheep_name = name;
}
}
if (navigator.onLine && Cloud.socket?.readyState === WebSocket.OPEN) {
Cloud.socket.send(JSON.stringify(message));
} else {
if (confirm("З'єднання розірвано! Перепідключитись?")) {
Cloud.start();
}
}
}
},
},
schedule: {
list: [],
genNull() {
return `
<div id="null-list" class="mess-list" data-visible="false">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path
d="M15,109.8l48,17c0,0,0,0,0,0c0.1,0,0.2,0.1,0.3,0.1c0.1,0,0.1,0,0.2,0c0.1,0,0.2,0,0.4,0c0,0,0.1,0,0.1,0c0.2,0,0.3,0,0.5,0 c0,0,0.1,0,0.1,0c0.1,0,0.2-0.1,0.3-0.1c0,0,0,0,0,0l48-17c1.2-0.4,2-1.6,2-2.8V73.4l10-3.5c0.8-0.3,1.5-1,1.8-1.8s0.2-1.8-0.3-2.6 l-12-20c0,0,0,0,0-0.1c0-0.1-0.1-0.1-0.1-0.2c0,0,0,0,0-0.1c0,0,0,0,0,0c0-0.1-0.1-0.1-0.1-0.2c0,0-0.1-0.1-0.1-0.1c0,0,0,0-0.1-0.1 c0,0,0,0-0.1,0c-0.1,0-0.1-0.1-0.2-0.1c0,0-0.1-0.1-0.1-0.1c0,0,0,0-0.1,0c0,0-0.1,0-0.1,0c-0.1,0-0.1-0.1-0.2-0.1 c-0.1,0-0.1,0-0.2-0.1c0,0,0,0-0.1,0c0,0,0,0,0,0l-48-17c-0.2-0.1-0.4-0.1-0.6-0.1c0,0-0.1,0-0.1,0c-0.2,0-0.3,0-0.5,0 c-0.1,0-0.1,0-0.2,0c-0.2,0-0.4,0.1-0.6,0.1l-48,17c0,0,0,0,0,0c0,0-0.1,0-0.1,0.1c0,0,0,0,0,0c-0.1,0.1-0.3,0.1-0.4,0.2 c0,0,0,0,0,0c0,0,0,0,0,0c-0.2,0.1-0.4,0.3-0.6,0.5l0,0c0,0-0.1,0.1-0.1,0.1c0,0,0,0,0,0c-0.1,0.1-0.1,0.2-0.2,0.2c0,0,0,0,0,0 c0,0,0,0-0.1,0.1l-12,20C1,66.2,0.9,67.2,1.2,68s1,1.5,1.8,1.8l10,3.5V107C13,108.3,13.8,109.4,15,109.8z M109,104.9l-42,14.9V95.7 c0-1.7-1.3-3-3-3s-3,1.3-3,3v24.1l-42-14.9V75.5l32,11.3c0.3,0.1,0.7,0.2,1,0.2c1,0,2-0.5,2.6-1.5L64,69.8l9.4,15.7 C74,86.5,75,87,76,87c0.3,0,0.7-0.1,1-0.2l32-11.3V104.9z M67,34.2L103,47L67,59.8V34.2z M77.3,80.4l-8.9-14.8l42.2-15l8.9,14.8 L77.3,80.4z M17.3,50.6l42.2,15l-8.9,14.8l-42.2-15L17.3,50.6z"
/>
</svg>
<h3>Доступних днів поки немає</h3>
</div>
<div id="load-list" class="mess-list" data-visible="false">
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0"
width="64px"
height="64px"
viewBox="0 0 128 128"
xml:space="preserve"
>
<g>
<path
d="M64 9.75A54.25 54.25 0 0 0 9.75 64H0a64 64 0 0 1 128 0h-9.75A54.25 54.25 0 0 0 64 9.75z"
></path>
<animateTransform
attributeName="transform"
type="rotate"
from="0 64 64"
to="360 64 64"
dur="1400ms"
repeatCount="indefinite"
></animateTransform>
</g>
</svg>
<h3>Зачекайте ...</h3>
</div>
`
},
async setHTML() {
const block = document.getElementById('stand-schedule');
block.innerHTML = this.genNull();
const null_list = document.getElementById('null-list');
const load_list = document.getElementById('load-list');
null_list.setAttribute("data-visible", "false");
load_list.setAttribute("data-visible", "true");
const url = `${CONFIG.api}stand/schedule/list/${Stand_card.id}`;
this.list = Stand_card.grouped(await Stand_card.loadAPI(url));
if (this.list.length == 0) {
null_list.setAttribute("data-visible", "true");
load_list.setAttribute("data-visible", "false");
} else {
null_list.setAttribute("data-visible", "false");
load_list.setAttribute("data-visible", "false");
}
const fragment = document.createDocumentFragment();
const createSelect = (sheep) => {
const select = document.createElement("select");
select.id = `name-${sheep.id}`;
// пустой option
select.appendChild(Object.assign(document.createElement("option"), {
value: "",
textContent: " "
}));
// якщо є права менеджера - додаємо всіх користувачів
if (USER.possibilities.can_manager_stand && Array.isArray(Stand_card.info.sheeps)) {
Stand_card.info.sheeps.sort((a, b) => a.name.localeCompare(b.name, 'uk'));
Stand_card.info.sheeps.forEach(s => {
const option = document.createElement("option");
option.value = s.id;
option.textContent = s.name;
if (s.id === sheep.sheep_id) option.selected = true;
select.appendChild(option);
});
} else {
// якщо є власник - показуємо його
const opt = document.createElement("option");
if (sheep.sheep_id) {
opt.value = sheep.sheep_id;
opt.textContent = sheep.sheep_name ?? USER.name;
opt.selected = sheep.sheep_id === USER.id;
} else {
opt.value = USER.id;
opt.textContent = USER.name;
}
select.appendChild(opt);
}
// якщо зайнятий іншим користувачем - блокуємо
if (sheep.sheep_id && sheep.sheep_id !== USER.id && !USER.possibilities.can_manager_stand) {
select.disabled = true;
select.value = sheep.sheep_id;
}
// --- обробники ---
select.addEventListener("mousedown", () => Stand_card.cloud.mess.locking({ id: sheep.id }));
select.addEventListener("change", () => Stand_card.cloud.mess.update({ sheep_id: select.value, id: sheep.id }));
select.addEventListener("blur", () => Stand_card.cloud.mess.unlocking({ id: sheep.id }));
return select;
};
this.list.forEach((day, dayIndex) => {
const timestamp = day[0][0].date;
const dayDiv = Object.assign(document.createElement("div"), {
className: "block-day",
id: `day-${dayIndex}`
});
dayDiv.appendChild(Object.assign(document.createElement("h3"), {
textContent: `${formattedDate(timestamp)}${formattedDayName(timestamp)}`
}));
const step = day[1]?.[0]?.hour - day[0]?.[0]?.hour || Stand_card.info.list.processing_time; // крок між інтервалами
day.forEach((hour, hourIndex) => {
const hourDiv = Object.assign(document.createElement("div"), {
id: `hour-${dayIndex}-${hourIndex}`
});
const start = hour[0].hour;
const end = start + step;
hourDiv.appendChild(Object.assign(document.createElement("span"), {
className: "time",
textContent: `${Stand_card.formatTime(start)}-${Stand_card.formatTime(end)}`
}));
hour.forEach(sheep => hourDiv.appendChild(createSelect(sheep)));
dayDiv.appendChild(hourDiv);
});
fragment.appendChild(dayDiv);
});
// кнопка додавання
if (USER.possibilities.can_add_stand) {
const btn = Object.assign(document.createElement("button"), {
id: "stand-new-button",
textContent: "Додати день",
onclick: () => Stand_card.addStand()
});
fragment.appendChild(btn);
}
block.appendChild(fragment);
}
},
grouped(list) {
const groupedByDate = {};
for (const item of list) {
if (!groupedByDate[item.date]) groupedByDate[item.date] = [];
groupedByDate[item.date].push(item);
}
const result = Object.values(groupedByDate).map(dateGroup => {
const groupedByHour = [];
let currentGroup = [];
let lastHour = null;
for (const item of dateGroup) {
if (item.hour !== lastHour) {
if (currentGroup.length > 0) groupedByHour.push(currentGroup);
currentGroup = [];
lastHour = item.hour;
}
currentGroup.push(item);
}
if (currentGroup.length > 0) groupedByHour.push(currentGroup);
return groupedByHour;
});
return result;
},
formatTime(hours) {
let h = Math.floor(hours);
let m = (hours % 1 === 0.5) ? "30" : "00";
let hh = h.toString().padStart(2, "0");
return `${hh}:${m}`;
},
async addStand() {
const button = document.getElementById('stand-new-button');
const uuid = localStorage.getItem('uuid');
const URL = `${CONFIG.api}stand/schedule/${Stand_card.id}`;
await fetch(URL, {
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": uuid
}
})
.then(response => {
if (response.status == 200) {
console.log({ 'setPack': 'ok' });
button.innerText = "День додано";
Notifier.success('День додано');
return response.json()
} else {
console.log('err');
button.innerText = "Помилка запису";
Notifier.error('Помилка додавання');
return
}
})
.then(data => {
console.log(data);
Stand_card.schedule.setHTML();
setTimeout(() => {
button.innerText = "Додати день";
}, 3000);
})
.catch(err => {
console.log(err);
button.innerText = "Помилка запису";
})
}
}