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: [], async setHTML() { const url = `${CONFIG.api}stand/${Stand_card.id}`; this.list = await Stand_card.loadAPI(url); document.getElementById('stand-info-title').innerText = this.list.title; 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-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: " " })); // заполняем всех овечек Sheeps.sheeps_list.list.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 = ``; el.removeAttribute("disabled"); } else if (!sid) { el.innerHTML = ``; el.removeAttribute("disabled"); } else { el.innerHTML = ``; el.disabled = true; } } el.style.border = ""; el.style.backgroundColor = ""; break; } default: return; } }, // update(msg) { // console.log(msg.type, msg.data.id); // if (msg.type == "stand_locking") { // const id = msg.data.id; // const el = document.getElementById(`name-${id}`); // if (msg.user.id != USER.id) { // el.disabled = true; // el.style.border = "2px solid var(--PrimaryColor);" // el.style.backgroundColor = "red" // } // } else if (msg.type == "stand_unlocking") { // const id = msg.data.id; // const el = document.getElementById(`name-${id}`); // if (msg.user.id != USER.id || !msg.data.sheep_id) { // el.style.border = ""; // el.style.backgroundColor = "" // el.removeAttribute('disabled'); // } // } else if (msg.type == "stand_update") { // const id = msg.data.id; // const el = document.getElementById(`name-${id}`); // if (msg.data.sheep_id == USER.id) { // el.innerHTML = ``; // el.removeAttribute('disabled'); // } else if (msg.data.sheep_id == "" || msg.data.sheep_id == null || msg.data.sheep_id == USER.id) { // el.innerHTML = ``; // el.removeAttribute('disabled'); // } else { // el.innerHTML = ``; // el.disabled = true; // } // el.style.border = ""; // el.style.backgroundColor = "" // } else 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 (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 (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 = Sheeps.sheeps_list.list.map(e => e.id).indexOf(Number(sheep_id)); if (pos != -1) { let name = Sheeps.sheeps_list.list[pos].name; message.data.sheep_name = name; } } if (Cloud.socket?.readyState === WebSocket.OPEN) { Cloud.socket.send(JSON.stringify(message)); } else { if (confirm("З'єднання розірвано! Перепідключитись?")) { Cloud.start(); } } } }, }, schedule: { list: [], async setHTML() { const block = document.getElementById('stand-schedule'); block.innerHTML = ''; const url = `${CONFIG.api}stand/schedule/list/${Stand_card.id}`; this.list = Stand_card.grouped(await Stand_card.loadAPI(url)); 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(Sheeps?.sheeps_list?.list)) { Sheeps.sheeps_list.list.sort((a, b) => a.name.localeCompare(b.name, 'uk')); Sheeps.sheeps_list.list.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)}` })); day.forEach((hour, hourIndex) => { const hourDiv = Object.assign(document.createElement("div"), { id: `hour-${dayIndex}-${hourIndex}` }); hourDiv.appendChild(Object.assign(document.createElement("span"), { className: "time", textContent: `${Stand_card.formatTime(hour[0].hour)}-${Stand_card.formatTime(hour[0].hour + Stand_card.info.list.processing_time)}` })); 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); } // async setHTML() { // const block_schedule = document.getElementById('stand-schedule'); // block_schedule.innerHTML = ''; // const url = `${CONFIG.api}stand/schedule/list/${Stand_card.id}`; // this.list = Stand_card.grouped(await Stand_card.loadAPI(url)); // let dayIndex = 0; // for (const day of this.list) { // const timestamp = day[0][0].date; // const dayDiv = document.createElement("div"); // dayDiv.className = "block-day"; // dayDiv.id = `day-${dayIndex}`; // const header = document.createElement("h3"); // header.textContent = `${formattedDate(timestamp)} • ${formattedDayName(timestamp)}`; // dayDiv.appendChild(header); // let hourIndex = 0; // for (const hour of day) { // const hourDiv = document.createElement("div"); // hourDiv.id = `hour-${dayIndex}-${hourIndex}`; // const span = document.createElement("span"); // span.className = "time"; // span.textContent = `${Stand_card.formatTime(hour[0].hour)}-${Stand_card.formatTime(hour[0].hour + Stand_card.info.list.processing_time)}`; // hourDiv.appendChild(span); // for (const sheep of hour) { // const select = document.createElement("select"); // select.id = `name-${sheep.id}`; // const emptyOption = document.createElement("option"); // emptyOption.value = ""; // emptyOption.textContent = " "; // select.appendChild(emptyOption); // if (sheep.sheep_id && sheep.sheep_id == USER.id) { // const opt = document.createElement("option"); // opt.value = USER.id; // opt.textContent = USER.name; // opt.selected = true; // select.appendChild(opt); // // --- Обработчики событий --- // select.addEventListener("focus", () => { // // пользователь начал взаимодействие → блокируем // Stand_card.cloud.mess.locking({ id: sheep.id }); // }); // select.addEventListener("blur", () => { // // пользователь ушёл → разблокируем // Stand_card.cloud.mess.unlocking({ id: sheep.id }); // }); // select.addEventListener("change", () => { // // пользователь выбрал что-то → обновляем // Stand_card.cloud.mess.update({ sheep_id: select.value, id: sheep.id }); // }); // } else if (sheep.sheep_id && sheep.sheep_id != USER.id) { // const opt = document.createElement("option"); // opt.value = sheep.sheep_id; // opt.textContent = sheep.sheep_name; // opt.selected = true; // select.appendChild(opt); // // --- Обработчики событий --- // select.addEventListener("focus", () => { // // пользователь начал взаимодействие → блокируем // Stand_card.cloud.mess.locking({ id: sheep.id }); // }); // select.addEventListener("blur", () => { // // пользователь ушёл → разблокируем // Stand_card.cloud.mess.unlocking({ id: sheep.id }); // }); // select.addEventListener("change", () => { // // пользователь выбрал что-то → обновляем // Stand_card.cloud.mess.update({ sheep_id: select.value, id: sheep.id }); // }); // select.disabled = true; // } else { // const opt = document.createElement("option"); // opt.value = USER.id; // opt.textContent = USER.name; // select.appendChild(opt); // // --- Обработчики событий --- // select.addEventListener("focus", () => { // // пользователь начал взаимодействие → блокируем // Stand_card.cloud.mess.locking({ id: sheep.id }); // }); // select.addEventListener("blur", () => { // // пользователь ушёл → разблокируем // Stand_card.cloud.mess.unlocking({ id: sheep.id }); // }); // select.addEventListener("change", () => { // // пользователь выбрал что-то → обновляем // Stand_card.cloud.mess.update({ sheep_id: select.value, id: sheep.id }); // }); // } // hourDiv.appendChild(select); // } // dayDiv.appendChild(hourDiv); // hourIndex++; // } // block_schedule.appendChild(dayDiv); // dayIndex++; // } // if (USER.possibilities.can_add_stand) { // const btn = document.createElement("button"); // btn.id = "stand-new-button"; // btn.onclick = () => Stand_card.addStand(); // btn.textContent = "Додати стенд(и)"; // block_schedule.appendChild(btn); // } // } }, 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 = "Стенд(и) додано"; return response.json() } else { console.log('err'); button.innerText = "Помилка запису"; return } }) .then(data => { console.log(data); Stand_card.schedule.setHTML(); setTimeout(() => { button.innerText = "Додати стенд(и)"; }, 3000); }) .catch(err => { console.log(err); button.innerText = "Помилка запису"; }) }, edit({ sheep_id, stand_id }) { } }