Додане повідомлення про оновлення застосунку Оновлен Service Worker Перероблен WebSocket APІ
541 lines
22 KiB
JavaScript
541 lines
22 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: [],
|
||
|
||
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 = `<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;
|
||
}
|
||
},
|
||
|
||
// 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 = `<option value=""></option><option selected value="${USER.id}">${USER.name}</option>`;
|
||
// el.removeAttribute('disabled');
|
||
// } else if (msg.data.sheep_id == "" || msg.data.sheep_id == null || msg.data.sheep_id == USER.id) {
|
||
// el.innerHTML = `<option value=""></option><option value="${USER.id}">${USER.name}</option>`;
|
||
// el.removeAttribute('disabled');
|
||
// } else {
|
||
// el.innerHTML = `<option value="${msg.data.sheep_id}">${msg.data.sheep_name}</option>`;
|
||
// 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 }) {
|
||
|
||
}
|
||
} |