445 lines
18 KiB
JavaScript
445 lines
18 KiB
JavaScript
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 = "Помилка запису";
|
||
})
|
||
}
|
||
} |