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