5677 lines
239 KiB
JavaScript
Executable File
5677 lines
239 KiB
JavaScript
Executable File
"use strict";
|
||
|
||
if (typeof window.tasks !== "function") {
|
||
// Not initialized
|
||
|
||
// Initialize of the class in global namespace
|
||
window.tasks = class tasks {
|
||
/**
|
||
* Заблокировать функцию закрытия всплывающего окна?
|
||
*/
|
||
static freeze = false;
|
||
|
||
/**
|
||
* Тело всплывающего окна (массив)
|
||
*/
|
||
static body = {};
|
||
|
||
/**
|
||
* Инициализирован класс?
|
||
*/
|
||
static initialized = false;
|
||
|
||
/**
|
||
* Создать заявку
|
||
*
|
||
* @param {HTMLElement} cashiers Количество кассиров <input>
|
||
* @param {HTMLElement} displayers Количество выкладчиков <input>
|
||
* @param {HTMLElement} loaders Количество грузчиков <input>
|
||
* @param {HTMLElement} gastronomes Количество гастрономов <input>
|
||
* @param {HTMLElement} start Начало работы (00:00) <input>
|
||
* @param {HTMLElement} end Конец работы (00:00) <input>
|
||
* @param {HTMLElement} date Дата работы (d.m.Y) <input>
|
||
* @param {HTMLElement} button Кнопка заявки <button>
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _create(
|
||
cashiers,
|
||
displayers,
|
||
loaders,
|
||
gastronomes,
|
||
start,
|
||
end,
|
||
date,
|
||
button,
|
||
) {
|
||
// Блокировка полей ввода
|
||
cashiers.setAttribute("readonly", true);
|
||
displayers.setAttribute("readonly", true);
|
||
loaders.setAttribute("readonly", true);
|
||
gastronomes.setAttribute("readonly", true);
|
||
start.setAttribute("readonly", true);
|
||
end.setAttribute("readonly", true);
|
||
date.setAttribute("readonly", true);
|
||
|
||
// Блокировка кнопки
|
||
button.setAttribute("disabled", true);
|
||
|
||
// Запуск выполнения
|
||
this.__create(
|
||
cashiers,
|
||
displayers,
|
||
loaders,
|
||
gastronomes,
|
||
start,
|
||
end,
|
||
date,
|
||
button,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Создать заявку
|
||
*
|
||
* @param {HTMLElement} cashiers Количество кассиров <input>
|
||
* @param {HTMLElement} displayers Количество выкладчиков <input>
|
||
* @param {HTMLElement} loaders Количество грузчиков <input>
|
||
* @param {HTMLElement} gastronomes Количество гастрономов <input>
|
||
* @param {HTMLElement} start Начало работы (00:00) <input>
|
||
* @param {HTMLElement} end Конец работы (00:00) <input>
|
||
* @param {HTMLElement} date Дата работы (d.m.Y) <input>
|
||
* @param {HTMLElement} button Кнопка заявки <button>
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static __create = damper(
|
||
async (
|
||
cashiers,
|
||
displayers,
|
||
loaders,
|
||
gastronomes,
|
||
start,
|
||
end,
|
||
date,
|
||
button,
|
||
) => {
|
||
// Инициализация функции разблокировки
|
||
function unblock() {
|
||
// Разблокировка полей ввода
|
||
cashiers.removeAttribute("readonly");
|
||
displayers.removeAttribute("readonly");
|
||
loaders.removeAttribute("readonly");
|
||
gastronomes.removeAttribute("readonly");
|
||
start.removeAttribute("readonly");
|
||
end.removeAttribute("readonly");
|
||
date.removeAttribute("readonly");
|
||
|
||
// Разблокировка кнопки
|
||
button.removeAttribute("disabled");
|
||
}
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch("/tasks/create", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body:
|
||
`cashiers=${cashiers.value}&displayers=${displayers.value}&loaders=${loaders.value}&gastronomes=${gastronomes.value}&start=${start.value}&end=${end.value}&date=${
|
||
date.valueAsDate / 1000
|
||
}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Разблокировка полей ввода и кнопок
|
||
unblock();
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Деинициализация активного окна
|
||
this.body.wrap.remove();
|
||
}
|
||
|
||
// Реинициализация строк
|
||
this.reinit();
|
||
}
|
||
});
|
||
},
|
||
300,
|
||
);
|
||
|
||
/**
|
||
* Сгенерировать окно создания заявок
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static create = damper(() => {
|
||
// Инициализация оболочки всплывающего окна
|
||
this.body.wrap = document.createElement("div");
|
||
this.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("list", "small");
|
||
|
||
// Инициализация заголовка всплывающего окна
|
||
const title = document.createElement("h3");
|
||
title.classList.add("unselectable");
|
||
title.innerText = "Создание заявки";
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const tasks = document.createElement("div");
|
||
tasks.classList.add("column");
|
||
tasks.setAttribute("data-column", "tasks");
|
||
|
||
// Инициализация строки
|
||
const row_1 = document.createElement("div");
|
||
row_1.classList.add("row", "merged");
|
||
|
||
// Инициализация оболочки для строки с кассирами
|
||
const cashier = document.createElement("label");
|
||
cashier.setAttribute("id", "cashier");
|
||
|
||
// Инициализация заголовка для поля ввода кассиров
|
||
const cashier_title = document.createElement("b");
|
||
cashier_title.classList.add("separated", "right");
|
||
cashier_title.innerText = "Кассиры:";
|
||
|
||
// Инициализация поля ввода кассиров
|
||
const cashier_input = document.createElement("input");
|
||
cashier_input.classList.add("small", "center", "cloud");
|
||
cashier_input.setAttribute("type", "number");
|
||
cashier_input.setAttribute("min", "0");
|
||
cashier_input.setAttribute("max", "50");
|
||
cashier_input.setAttribute("title", "Количество кассиров");
|
||
cashier_input.value = 0;
|
||
|
||
// Инициализация строки
|
||
const row_2 = document.createElement("div");
|
||
row_2.classList.add("row", "merged");
|
||
|
||
// Инициализация оболочки для строки с выкладчиками
|
||
const displayer = document.createElement("label");
|
||
displayer.setAttribute("id", "displayer");
|
||
|
||
// Инициализация заголовка для поля ввода выкладчиков
|
||
const displayer_title = document.createElement("b");
|
||
displayer_title.classList.add("separated", "right");
|
||
displayer_title.innerText = "Выкладчики:";
|
||
|
||
// Инициализация поля ввода выкладчиков
|
||
const displayer_input = document.createElement("input");
|
||
displayer_input.classList.add("small", "center", "cloud");
|
||
displayer_input.setAttribute("type", "number");
|
||
displayer_input.setAttribute("min", "0");
|
||
displayer_input.setAttribute("max", "50");
|
||
displayer_input.setAttribute("title", "Количество выкладчиков");
|
||
displayer_input.value = 0;
|
||
|
||
// Инициализация строки
|
||
const row_3 = document.createElement("div");
|
||
row_3.classList.add("row", "merged");
|
||
|
||
// Инициализация оболочки для строки с грузчиками
|
||
const loader = document.createElement("label");
|
||
loader.setAttribute("id", "loader");
|
||
|
||
// Инициализация заголовка для поля ввода грузчиков
|
||
const loader_title = document.createElement("b");
|
||
loader_title.classList.add("separated", "right");
|
||
loader_title.innerText = "Грузчики:";
|
||
|
||
// Инициализация поля ввода грузчиков
|
||
const loader_input = document.createElement("input");
|
||
loader_input.classList.add("small", "center", "cloud");
|
||
loader_input.setAttribute("type", "number");
|
||
loader_input.setAttribute("min", "0");
|
||
loader_input.setAttribute("max", "50");
|
||
loader_input.setAttribute("title", "Количество грузчиков");
|
||
loader_input.value = 0;
|
||
|
||
// Инициализация строки
|
||
const row_4 = document.createElement("div");
|
||
row_4.classList.add("row", "merged");
|
||
|
||
// Инициализация оболочки для строки с гастрономами
|
||
const gastronome = document.createElement("label");
|
||
gastronome.setAttribute("id", "gastronome");
|
||
|
||
// Инициализация заголовка для поля ввода гастрономов
|
||
const gastronome_title = document.createElement("b");
|
||
gastronome_title.classList.add("separated", "right");
|
||
gastronome_title.innerText = "Гастрономы:";
|
||
|
||
// Инициализация поля ввода гастрономов
|
||
const gastronome_input = document.createElement("input");
|
||
gastronome_input.classList.add("small", "center", "cloud");
|
||
gastronome_input.setAttribute("type", "number");
|
||
gastronome_input.setAttribute("min", "0");
|
||
gastronome_input.setAttribute("max", "50");
|
||
gastronome_input.setAttribute("title", "Количество гастрономов");
|
||
gastronome_input.value = 0;
|
||
|
||
// Инициализация строки
|
||
const row_5 = document.createElement("div");
|
||
row_5.classList.add("row", "buttons");
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons = document.createElement("label");
|
||
|
||
// Инициализация поля ввода времени начала
|
||
const start_input = document.createElement("input");
|
||
start_input.classList.add("cloud");
|
||
start_input.setAttribute("type", "time");
|
||
start_input.setAttribute("title", "Время начала работы по заявке");
|
||
start_input.value = "09:00";
|
||
|
||
// Инициализация поля ввода времени окончания
|
||
const end_input = document.createElement("input");
|
||
end_input.classList.add("cloud");
|
||
end_input.setAttribute("type", "time");
|
||
end_input.setAttribute("title", "Время конца работы по заявке");
|
||
end_input.value = "18:00";
|
||
|
||
// Инициализация поля ввода даты (дня)
|
||
const day_input = document.createElement("input");
|
||
day_input.classList.add("cloud");
|
||
day_input.setAttribute("type", "date");
|
||
day_input.setAttribute("title", "Дата заявки");
|
||
const tomorrow = new Date();
|
||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||
day_input.valueAsDate = tomorrow;
|
||
|
||
// Инициализация кнопки подтверждения даты
|
||
const create = document.createElement("button");
|
||
create.classList.add("grass");
|
||
create.innerText = "Создать";
|
||
create.setAttribute(
|
||
"onclick",
|
||
`tasks._create(this.parentElement.parentElement.previousElementSibling.previousElementSibling.previousElementSibling.previousElementSibling.children[0].children[1], this.parentElement.parentElement.previousElementSibling.previousElementSibling.previousElementSibling.children[0].children[1], this.parentElement.parentElement.previousElementSibling.previousElementSibling.children[0].children[1], this.parentElement.parentElement.previousElementSibling.children[0].children[1], this.previousElementSibling.previousElementSibling.previousElementSibling, this.previousElementSibling.previousElementSibling, this.previousElementSibling, this)`,
|
||
);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.body.errors = document.createElement("section");
|
||
this.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const errors = document.createElement("section");
|
||
errors.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Запись в документ
|
||
popup.appendChild(title);
|
||
|
||
cashier.appendChild(cashier_title);
|
||
cashier.appendChild(cashier_input);
|
||
row_1.appendChild(cashier);
|
||
tasks.appendChild(row_1);
|
||
|
||
displayer.appendChild(displayer_title);
|
||
displayer.appendChild(displayer_input);
|
||
row_2.appendChild(displayer);
|
||
tasks.appendChild(row_2);
|
||
|
||
loader.appendChild(loader_title);
|
||
loader.appendChild(loader_input);
|
||
row_3.appendChild(loader);
|
||
tasks.appendChild(row_3);
|
||
|
||
gastronome.appendChild(gastronome_title);
|
||
gastronome.appendChild(gastronome_input);
|
||
row_4.appendChild(gastronome);
|
||
tasks.appendChild(row_4);
|
||
|
||
buttons.appendChild(start_input);
|
||
buttons.appendChild(end_input);
|
||
buttons.appendChild(day_input);
|
||
buttons.appendChild(create);
|
||
row_5.appendChild(buttons);
|
||
tasks.appendChild(row_5);
|
||
|
||
main.appendChild(tasks);
|
||
popup.appendChild(main);
|
||
|
||
this.body.wrap.appendChild(popup);
|
||
document.body.appendChild(this.body.wrap);
|
||
|
||
errors.appendChild(dl);
|
||
this.body.errors.appendChild(errors);
|
||
this.body.wrap.appendChild(this.body.errors);
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.body.errors);
|
||
const resize = new ResizeObserver(() => top(this.body.errors));
|
||
resize.observe(this.body.wrap);
|
||
|
||
// Инициализация функции блокировки кнопки
|
||
const block = () => {
|
||
if (
|
||
cashier_input.value > 0 ||
|
||
displayer_input.value > 0 ||
|
||
loader_input.value > 0 ||
|
||
gastronome_input.value > 0
|
||
) create.removeAttribute("disabled");
|
||
else create.setAttribute("disabled", "true");
|
||
};
|
||
|
||
// Добавление функции блокировки кнопки по событиям
|
||
cashier_input.addEventListener("keyup", block);
|
||
displayer_input.addEventListener("keyup", block);
|
||
loader_input.addEventListener("keyup", block);
|
||
gastronome_input.addEventListener("keyup", block);
|
||
cashier_input.addEventListener("change", block);
|
||
displayer_input.addEventListener("change", block);
|
||
loader_input.addEventListener("change", block);
|
||
gastronome_input.addEventListener("change", block);
|
||
|
||
// Первичная активация функции блокировки кнопки
|
||
block();
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.body.wrap.remove();
|
||
|
||
// Удаление статуса активной строки
|
||
row.removeAttribute("data-selected");
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () => this.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () => this.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (const select of popup.getElementsByTagName("select")) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
|
||
for (const option of select.getElementsByTagName("option")) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (const textarea of popup.getElementsByTagName("textarea")) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.body.errors)).observe(textarea, {
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
});
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener("focus", () => this.freeze = true);
|
||
textarea.addEventListener("focusout", () => this.freeze = false);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "создать"
|
||
create.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
cashier_input.focus();
|
||
}, 300);
|
||
|
||
/**
|
||
* Записать или переключить фильтр (0, 1, 2)
|
||
*
|
||
* @param {string} name Название
|
||
* @param {string|number|null} value Значение
|
||
* @param {HTMLElement|null} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static filter = damper(async (name, value, button) => {
|
||
if (typeof name === "string") {
|
||
// Получено название
|
||
|
||
// Инициализация сериализованного пути к директории
|
||
const path = `tasks_filter_${name}`;
|
||
|
||
if (typeof value === "string" || typeof value === "number") {
|
||
// Получено значение
|
||
|
||
// Запись нового значения
|
||
buffer.write(path, value);
|
||
} else {
|
||
// Не получено значение
|
||
|
||
// Чтение текущего значения
|
||
value = +(await buffer.read(path));
|
||
|
||
// Инициализация значения по умолчанию
|
||
if (isNaN(value)) value = 0;
|
||
|
||
// Запись нового значения (инвертирование)
|
||
buffer.write(path, ++value < 3 ? value : 0);
|
||
|
||
if (button instanceof HTMLElement) {
|
||
// Получена кнопка
|
||
|
||
// Деинициализация классов статуса фильтра
|
||
button.classList.remove("earth", "sand", "river");
|
||
|
||
// Инициализация классов статуса фильтра
|
||
if (value === 1) button.classList.add("sand");
|
||
else if (value === 2) button.classList.add("river");
|
||
else button.classList.add("earth");
|
||
}
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Записать фильтр поиска
|
||
*
|
||
* @param {HTMLElement} input Строка поиска
|
||
* @param {HTMLElement} button Кнопка отправки
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static search = (input, button) => {
|
||
input.setAttribute("readonly", true);
|
||
button.setAttribute("disabled", true);
|
||
|
||
this._search(input, button);
|
||
};
|
||
|
||
/**
|
||
* Записать фильтр поиска (системное)
|
||
*
|
||
* Используется как оболочка для большей временной задержки
|
||
*
|
||
* @param {HTMLElement} input Строка поиска
|
||
* @param {HTMLElement} button Кнопка отправки
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _search = damper((input, button) => {
|
||
this.filter("search", input.value);
|
||
this.reinit(() => {
|
||
input.removeAttribute("readonly");
|
||
button.removeAttribute("disabled");
|
||
});
|
||
}, 1000);
|
||
|
||
/**
|
||
* Инициализация страниц
|
||
*
|
||
* @param {number} amount Количество страниц для генерации
|
||
* @param {number} iteration Номер итерации (системное)
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static init(amount = 3, iteration = 0) {
|
||
if (
|
||
typeof amount === "number" && typeof iteration === "number" &&
|
||
this.initialized === false
|
||
) {
|
||
// Получены количество страниц и номер итерации
|
||
|
||
// Инициализация страницы
|
||
tasks.read(++iteration);
|
||
|
||
function generate() {
|
||
// Завершено выполнение итерации
|
||
|
||
// Деинициализация слушателя завершения текущей итерации
|
||
document.removeEventListener("tasks.read." + iteration, generate);
|
||
|
||
// Проверка условий и запуск следующей итерации
|
||
if (iteration < amount) tasks.init(amount, iteration);
|
||
else this.initialized = true;
|
||
}
|
||
|
||
// Инициализация слушателя завершения текущей итерации
|
||
document.addEventListener("tasks.read." + iteration, generate);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Реинициализация страниц
|
||
*
|
||
* @param {function} postprocessing Функция которая будет исполнена после реинициализации
|
||
* @param {number} amount Количество страниц для генерации при инициализации
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static reinit = damper((postprocessing, amount = 3) => {
|
||
if (typeof amount === "number") {
|
||
// Получены количество страниц
|
||
|
||
// Деинициализация cookie с номером страницы
|
||
Cookies.remove("tasks_page", { path: "/" });
|
||
|
||
// Инициализация оболочки
|
||
const tasks = document.getElementById("tasks");
|
||
|
||
if (tasks instanceof HTMLElement) {
|
||
// Найдена оболочка
|
||
|
||
// Инициализация буфера активного элемента
|
||
const active = document.activeElement;
|
||
|
||
// Инициализация буфера элементов
|
||
const buffer = document.createElement("div");
|
||
|
||
// Перенос элементов в буфер
|
||
buffer.replaceChildren(...tasks.children);
|
||
|
||
// Удаление комментариев с индексами страниц
|
||
tasks.innerHTML = tasks.innerHTML.replace(
|
||
/<!--(?!>)[\S\s]*?-->/g,
|
||
"",
|
||
);
|
||
|
||
// Перенос элементов из буфера
|
||
tasks.replaceChildren(...buffer.children);
|
||
|
||
// Деинициализация страниц
|
||
for (
|
||
const row of tasks.querySelectorAll(
|
||
':scope > div[data-row="task"]',
|
||
)
|
||
) row.remove();
|
||
|
||
// Сброс статуса инициализированности
|
||
this.initialized = false;
|
||
|
||
// Инициализация страниц
|
||
this.init(amount);
|
||
|
||
// Возвращение фокуса на активный элемент
|
||
active.focus();
|
||
|
||
// Постобработка
|
||
if (typeof postprocessing === "function") postprocessing();
|
||
}
|
||
}
|
||
}, 2000);
|
||
|
||
/**
|
||
* Прочитать
|
||
*
|
||
* Читает список задач и модифицирует документ (записывает в общий список)
|
||
*
|
||
* @param {number} page Страница
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static read = damper(async (page) => {
|
||
if (typeof page !== "number") {
|
||
// Не получена страница
|
||
|
||
// Инициализация страницы (если не получена, то брать из cookie и прибавлять 1)
|
||
page = Cookies.get(`tasks_page`) ?? 0;
|
||
|
||
// Запись идентификатора следующей страницы
|
||
++page;
|
||
}
|
||
|
||
// Запрос к серверу
|
||
await fetch("/tasks/read", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `page=${page}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
// Инициализация буфера для записи в документ
|
||
const element = document.createElement("div");
|
||
|
||
// Запись пустого элемента из буфера в документ
|
||
document.getElementById("tasks").appendChild(element);
|
||
|
||
// Запись в документ HTML-данных через буфер
|
||
element.outerHTML = data.rows;
|
||
|
||
// Запуск реинициализатора строк
|
||
this.reinitializer.start();
|
||
|
||
// Вызов события: "итерация чтения завершена"
|
||
document.dispatchEvent(new CustomEvent("tasks.read." + page));
|
||
}
|
||
});
|
||
}, 50);
|
||
|
||
/**
|
||
* Сгенерировать всплывающее окно c данными задачи
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static popup = damper(async (row) => {
|
||
if (row instanceof HTMLElement) {
|
||
// Получена строка
|
||
|
||
// Инициализация идентификатора сотрудника
|
||
const task = row.getAttribute("id");
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${task}/read`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки
|
||
|
||
if ((Date.now() / 1000 | 0) - data.start > 0) {
|
||
// Началась заявка (возможно уже закончилась)
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.body.wrap = document.createElement("div");
|
||
this.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("list", "small");
|
||
|
||
// Инициализация заголовка всплывающего окна
|
||
const title = document.createElement("h3");
|
||
title.classList.add("unselectable");
|
||
title.innerText = task;
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация буфера для записи во всплывающее окно
|
||
const list = document.createElement("div");
|
||
|
||
// Инициализация строки рейтинга
|
||
const rating_row = document.createElement("div");
|
||
rating_row.classList.add(
|
||
"row",
|
||
"divided",
|
||
"range",
|
||
"small",
|
||
"stretched",
|
||
);
|
||
|
||
// Инициализация ползунка выбора рейтинга
|
||
const rating_input = document.createElement("input");
|
||
rating_input.classList.add("cloud");
|
||
rating_input.setAttribute("type", "range");
|
||
rating_input.setAttribute("title", "Оценка");
|
||
rating_input.setAttribute("min", "1");
|
||
rating_input.setAttribute("max", "5");
|
||
rating_input.setAttribute("step", "1");
|
||
rating_input.setAttribute(
|
||
"oninput",
|
||
`this.nextElementSibling.innerText = this.value; setTimeout(() => {this.nextElementSibling.style.opacity = 1; setTimeout(() => this.nextElementSibling.style.transition = "0s", 100)}, 100); this.nextElementSibling.style.setProperty('--left', (((this.value - 1) / 4) * (this.offsetWidth - 24) + 12) + 'px');`,
|
||
);
|
||
|
||
// Инициализация значения в курсоре ползунка выбора рейтинга
|
||
const rating_value = document.createElement("i");
|
||
rating_value.classList.add("value", "unselectable");
|
||
rating_value.style.opacity = 0;
|
||
rating_value.style.transition = "0.1s ease-in";
|
||
rating_value.setAttribute("title", "Оценка");
|
||
rating_value.value = 5;
|
||
|
||
// Инициализация поля ввода отзыва
|
||
const review = document.createElement("textarea");
|
||
review.classList.add("snow");
|
||
review.setAttribute("autofocus", "true");
|
||
review.setAttribute("maxlength", "300");
|
||
review.setAttribute("title", "Отзыв");
|
||
review.setAttribute(
|
||
"placeholder",
|
||
"Отзыв о сотруднике",
|
||
);
|
||
|
||
// Запись актуальных значений
|
||
this.value(row, "completed").then(
|
||
(completed) => {
|
||
// Рейтинг
|
||
this.value(row, "rating")
|
||
.then((text) => {
|
||
if (completed) {
|
||
// Завершена заявка
|
||
|
||
// Блокировка
|
||
rating_input.setAttribute("disabled", "true");
|
||
}
|
||
|
||
// Запись актуального рейтинга
|
||
rating_input.value = text ?? 5;
|
||
});
|
||
|
||
// Отзыв
|
||
this.value(row, "review")
|
||
.then((text) => {
|
||
if (completed) {
|
||
// Завершена заявка
|
||
|
||
// Блокировка
|
||
review.setAttribute("readonly", "true");
|
||
}
|
||
|
||
// Запись актуального отзыва
|
||
review.value = text ?? "";
|
||
});
|
||
},
|
||
);
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons = document.createElement("div");
|
||
buttons.classList.add("row", "buttons");
|
||
|
||
// Инициализация кнопки заявления о проблеме
|
||
const problem = document.createElement("button");
|
||
problem.classList.add("clay", "stretched");
|
||
if (row.classList.contains("problematic")) {
|
||
// Проблемная заявка
|
||
|
||
problem.classList.add("grass");
|
||
problem.innerText = "Проблема решена";
|
||
|
||
problem.setAttribute(
|
||
"onclick",
|
||
`chat.send(this, null, document.getElementById('${task}'), 'market', 'solution', true, 'Проблема решена'); document.removeEventListener("keydown", tasks.buttons);`,
|
||
);
|
||
} else {
|
||
// Не проблемная заявка
|
||
|
||
problem.classList.add("clay");
|
||
problem.innerText = "Проблема";
|
||
|
||
problem.setAttribute(
|
||
"onclick",
|
||
`tasks.problem(document.getElementById('${task}'))`,
|
||
);
|
||
}
|
||
|
||
// Инициализация кнопки подтверждения
|
||
const complete = document.createElement("button");
|
||
complete.classList.add("grass", "stretched");
|
||
complete.innerText = "Завершить";
|
||
complete.setAttribute(
|
||
"onclick",
|
||
`tasks.complete(this, this.parentElement.previousElementSibling.previousElementSibling.children[0], this.parentElement.previousElementSibling, document.getElementById('${task}'))`,
|
||
);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.body.errors = document.createElement("section");
|
||
this.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const errors = document.createElement("section");
|
||
errors.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Удаление блокировки выполнения закрытия всплывающего окна
|
||
this.freeze = false;
|
||
|
||
// Запись в документ
|
||
popup.appendChild(title);
|
||
|
||
column.appendChild(list);
|
||
rating_row.appendChild(rating_input);
|
||
rating_row.appendChild(rating_value);
|
||
column.appendChild(rating_row);
|
||
column.appendChild(review);
|
||
|
||
if (data.completed !== true) {
|
||
// Зявка не завершена
|
||
|
||
buttons.appendChild(problem);
|
||
buttons.appendChild(complete);
|
||
column.appendChild(buttons);
|
||
}
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.body.wrap.appendChild(popup);
|
||
|
||
errors.appendChild(dl);
|
||
this.body.errors.appendChild(errors);
|
||
this.body.wrap.appendChild(this.body.errors);
|
||
|
||
document.body.appendChild(this.body.wrap);
|
||
|
||
// Запись в документ HTML-данных через буфер
|
||
list.outerHTML = data.task;
|
||
|
||
// Первичная инициализация (для генерации местоположения элемента со значением)
|
||
setTimeout(() => rating_input.oninput(), 300);
|
||
|
||
// Инициализация ошибок
|
||
this.errors(data.errors);
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.body.errors);
|
||
const resize = new ResizeObserver(() => top(this.body.errors));
|
||
resize.observe(this.body.wrap);
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () =>
|
||
this.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (const select of popup.getElementsByTagName("select")) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
|
||
for (const option of select.getElementsByTagName("option")) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (const textarea of popup.getElementsByTagName("textarea")) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.body.errors)).observe(
|
||
textarea,
|
||
{
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
},
|
||
);
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener("focus", () => this.freeze = true);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "завершить"
|
||
complete.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
review.focus();
|
||
} else {
|
||
// Не началась заявка
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.body.wrap = document.createElement("div");
|
||
this.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("list", "small");
|
||
|
||
// Инициализация заголовка всплывающего окна
|
||
const title = document.createElement("h3");
|
||
title.classList.add("unselectable");
|
||
title.innerText = task;
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация буфера для записи во всплывающее окно
|
||
const list = document.createElement("div");
|
||
|
||
// Инициализация оболочки для строки с полем ввода даты и времени, и кнопкой
|
||
const _row = document.createElement("div");
|
||
_row.classList.add("row", "divided");
|
||
|
||
// Инициализация оболочки для строки с полем ввода даты
|
||
const label = document.createElement("label");
|
||
|
||
// Инициализация поля ввода даты
|
||
const date = document.createElement("input");
|
||
date.classList.add("cloud");
|
||
date.setAttribute("type", "date");
|
||
date.setAttribute("title", "Дата заявки");
|
||
this.value(row, "date").then((text) =>
|
||
date.valueAsDate = new Date(text * 1000)
|
||
);
|
||
|
||
// Инициализация поля ввода времени начала
|
||
const start = document.createElement("input");
|
||
start.classList.add("cloud");
|
||
start.setAttribute("type", "time");
|
||
start.setAttribute("title", "Время начала работы");
|
||
this.value(row, "start").then((text) =>
|
||
start.value = typeof text === "string"
|
||
? (text.length === 7
|
||
? 0 + text.replace(/(?<=:00):00/, "")
|
||
: text.replace(/(?<=:00):00/, ""))
|
||
: "00:00"
|
||
);
|
||
|
||
// Инициализация поля ввода времени окончания
|
||
const end = document.createElement("input");
|
||
end.classList.add("cloud");
|
||
end.setAttribute("type", "time");
|
||
end.setAttribute("title", "Время конца работы");
|
||
this.value(row, "end").then((text) =>
|
||
end.value = typeof text === "string"
|
||
? (text.length === 7
|
||
? 0 + text.replace(/(?<=:00):00/, "")
|
||
: text.replace(/(?<=:00):00/, ""))
|
||
: "00:00"
|
||
);
|
||
|
||
// Инициализация кнопки подтверждения даты
|
||
const replace = document.createElement("button");
|
||
replace.classList.add("sea");
|
||
replace.innerText = "Записать";
|
||
replace.setAttribute(
|
||
"onclick",
|
||
`tasks.date(document.getElementById('${task}'), this.previousElementSibling.previousElementSibling.previousElementSibling, this.previousElementSibling.previousElementSibling, this.previousElementSibling, this)`,
|
||
);
|
||
|
||
// Инициализация списка выбора типа работы
|
||
const work = document.createElement("select");
|
||
work.classList.add("row", "connected", "stretched");
|
||
this.works(row).then((html) => work.innerHTML = html);
|
||
work.setAttribute("title", "Тип работы");
|
||
|
||
// Инициализация поля ввода описания
|
||
const description = document.createElement("textarea");
|
||
description.classList.add("snow");
|
||
this.value(row, "description").then((text) =>
|
||
description.value = text
|
||
);
|
||
description.setAttribute("autofocus", "true");
|
||
description.setAttribute("maxlength", "300");
|
||
description.setAttribute(
|
||
"onkeyup",
|
||
`tasks.description(document.getElementById('${task}'), this)`,
|
||
);
|
||
description.setAttribute("title", "Дополнительная информация");
|
||
description.setAttribute(
|
||
"placeholder",
|
||
"Дополнительная информация о требуемой работе",
|
||
);
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons_1 = document.createElement("div");
|
||
buttons_1.classList.add("row", "merged", "divided", "buttons");
|
||
|
||
// Инициализация кнопки подтверждения
|
||
const problem = document.createElement("button");
|
||
problem.classList.add("wide");
|
||
if (row.classList.contains("problematic")) {
|
||
// Проблемная заявка
|
||
|
||
problem.classList.add("grass");
|
||
problem.innerText = "Проблема решена";
|
||
|
||
problem.setAttribute(
|
||
"onclick",
|
||
`chat.send(this, null, document.getElementById('${task}'), 'market', 'solution', true, 'Проблема решена'); document.removeEventListener("keydown", tasks.buttons);`,
|
||
);
|
||
} else {
|
||
// Не проблемная заявка
|
||
|
||
problem.classList.add("clay");
|
||
problem.innerText = "Проблема";
|
||
|
||
problem.setAttribute(
|
||
"onclick",
|
||
`tasks.problem(document.getElementById('${task}'))`,
|
||
);
|
||
}
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons_2 = document.createElement("div");
|
||
buttons_2.classList.add("row", "merged", "buttons");
|
||
|
||
// Инициализация кнопки подтверждения
|
||
const confirm = document.createElement("button");
|
||
confirm.classList.add("wide");
|
||
if (row.classList.contains("confirmed")) {
|
||
// Подтверждена заявка
|
||
|
||
confirm.classList.add("clay");
|
||
confirm.innerText = "Отклонить";
|
||
} else {
|
||
// Не подтверждена заявка
|
||
|
||
confirm.classList.add("grass");
|
||
confirm.innerText = "Подтвердить";
|
||
}
|
||
confirm.setAttribute(
|
||
"onclick",
|
||
`tasks.confirm(this, document.getElementById('${task}'))`,
|
||
);
|
||
|
||
// Инициализация кнопки скрытия
|
||
const hide = document.createElement("button");
|
||
if (row.classList.contains("hided")) {
|
||
// Скрыта заявка
|
||
|
||
hide.classList.add("grass");
|
||
hide.innerText = "Показать";
|
||
} else {
|
||
// Не подтверждена заявка
|
||
|
||
hide.classList.add("sea");
|
||
hide.innerText = "Скрыть";
|
||
}
|
||
hide.setAttribute(
|
||
"onclick",
|
||
`tasks.hide(this, document.getElementById('${task}'))`,
|
||
);
|
||
|
||
// Инициализация кнопки удаления
|
||
const remove = document.createElement("button");
|
||
remove.classList.add("clay");
|
||
remove.innerText = "Удалить";
|
||
remove.setAttribute(
|
||
"onclick",
|
||
`tasks.remove(this, document.getElementById('${task}'))`,
|
||
);
|
||
|
||
if (
|
||
typeof core === "function" &&
|
||
(core.interface === "market" ||
|
||
core.interface === "worker")
|
||
) {
|
||
// Расширить кнопку для магазина и сотрудника
|
||
|
||
remove.classList.add("stretched");
|
||
}
|
||
|
||
// Инициализация окна с ошибками
|
||
this.body.errors = document.createElement("section");
|
||
this.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const errors = document.createElement("section");
|
||
errors.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Удаление блокировки выполнения закрытия всплывающего окна
|
||
this.freeze = false;
|
||
|
||
// Запись в документ
|
||
popup.appendChild(title);
|
||
|
||
column.appendChild(list);
|
||
label.appendChild(date);
|
||
label.appendChild(start);
|
||
label.appendChild(end);
|
||
label.appendChild(replace);
|
||
_row.appendChild(label);
|
||
column.appendChild(_row);
|
||
column.appendChild(work);
|
||
column.appendChild(description);
|
||
|
||
if (
|
||
typeof core === "function" &&
|
||
core.interface !== "market" &&
|
||
core.interface !== "worker"
|
||
) {
|
||
// Скрытие для магазина и сотрудника
|
||
|
||
buttons_1.appendChild(problem);
|
||
column.appendChild(buttons_1);
|
||
|
||
buttons_2.appendChild(confirm);
|
||
buttons_2.appendChild(hide);
|
||
} else {
|
||
// Отображение для магазина и сотрудника
|
||
|
||
problem.classList.add("stretched");
|
||
buttons_2.appendChild(problem);
|
||
}
|
||
|
||
buttons_2.appendChild(remove);
|
||
column.appendChild(buttons_2);
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.body.wrap.appendChild(popup);
|
||
|
||
errors.appendChild(dl);
|
||
this.body.errors.appendChild(errors);
|
||
this.body.wrap.appendChild(this.body.errors);
|
||
|
||
document.body.appendChild(this.body.wrap);
|
||
|
||
// Запись в документ HTML-данных через буфер
|
||
list.outerHTML = data.task;
|
||
|
||
// Инициализация ошибок
|
||
this.errors(data.errors);
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.body.errors);
|
||
const resize = new ResizeObserver(() => top(this.body.errors));
|
||
resize.observe(this.body.wrap);
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () =>
|
||
this.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (const select of popup.getElementsByTagName("select")) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
|
||
for (const option of select.getElementsByTagName("option")) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (const textarea of popup.getElementsByTagName("textarea")) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.body.errors)).observe(
|
||
textarea,
|
||
{
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
},
|
||
);
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener("focus", () => this.freeze = true);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "подтвердить"
|
||
confirm.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
description.focus();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Подтвердить
|
||
*
|
||
* @param {HTMLElement} button Кнопка
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static confirm = damper(async (button, row) => {
|
||
if (row instanceof HTMLElement && button instanceof HTMLElement) {
|
||
// Получена кнопка и строка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/confirm`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(document.getElementById("popup")) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
|
||
if (data.confirmed) {
|
||
// Подтверждена заявка
|
||
|
||
// Реинициализация текста
|
||
button.innerText = "Отклонить";
|
||
|
||
// Реинициализация стиля
|
||
button.classList.remove("grass");
|
||
button.classList.add("clay");
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.add("confirmed");
|
||
} else {
|
||
// Не подтверждена заявка
|
||
|
||
// Реинициализация текста
|
||
button.innerText = "Подтвердить";
|
||
|
||
// Реинициализация стиля
|
||
button.classList.remove("clay");
|
||
button.classList.add("grass");
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.remove("confirmed");
|
||
}
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.popup(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Сгенерировать всплывающее окно c заявлением о проблеме
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static problem = damper((row) => {
|
||
if (row instanceof HTMLElement) {
|
||
// Получена строка
|
||
|
||
if (row.classList.contains("problematic")) {
|
||
// Проблемная заявка
|
||
|
||
// Снятие статуса проблемной заявки
|
||
this.__problem(null, null, row);
|
||
} else {
|
||
// Не проблемная заявка
|
||
|
||
// Инициализация идентификатора сотрудника
|
||
const task = row.getAttribute("id");
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.body.wrap = document.createElement("div");
|
||
this.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("list", "small");
|
||
|
||
// Инициализация заголовка всплывающего окна
|
||
const title = document.createElement("h3");
|
||
title.classList.add("unselectable");
|
||
title.innerText = "Заявление о проблеме";
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация поля ввода отзыва
|
||
const problem = document.createElement("textarea");
|
||
problem.classList.add("snow");
|
||
problem.setAttribute("autofocus", "true");
|
||
problem.setAttribute("maxlength", "300");
|
||
problem.setAttribute("title", "Проблема");
|
||
problem.setAttribute(
|
||
"placeholder",
|
||
"Описание проблемы",
|
||
);
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons = document.createElement("div");
|
||
buttons.classList.add("row", "buttons");
|
||
|
||
// Инициализация кнопки подтверждения
|
||
const send = document.createElement("button");
|
||
send.classList.add("grass", "stretched");
|
||
send.innerText = "Отправить";
|
||
send.setAttribute(
|
||
"onclick",
|
||
`chat.send(this, this.parentElement.previousElementSibling, document.getElementById('${task}'), 'market', 'problem', true); document.removeEventListener("keydown", tasks.buttons);`,
|
||
);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.body.errors = document.createElement("section");
|
||
this.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const errors = document.createElement("section");
|
||
errors.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Удаление блокировки выполнения закрытия всплывающего окна
|
||
this.freeze = false;
|
||
|
||
// Запись в документ
|
||
popup.appendChild(title);
|
||
|
||
column.appendChild(problem);
|
||
buttons.appendChild(send);
|
||
column.appendChild(buttons);
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.body.wrap.appendChild(popup);
|
||
|
||
errors.appendChild(dl);
|
||
this.body.errors.appendChild(errors);
|
||
this.body.wrap.appendChild(this.body.errors);
|
||
|
||
document.body.appendChild(this.body.wrap);
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.body.errors);
|
||
const resize = new ResizeObserver(() => top(this.body.errors));
|
||
resize.observe(this.body.wrap);
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () => this.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (const select of popup.getElementsByTagName("select")) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
|
||
for (const option of select.getElementsByTagName("option")) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (const textarea of popup.getElementsByTagName("textarea")) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.body.errors)).observe(
|
||
textarea,
|
||
{
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
},
|
||
);
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener("focus", () => this.freeze = true);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "отправить"
|
||
send.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
problem.focus();
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Проблемная заявка (вызов демпфера)
|
||
*
|
||
* Изменить статус заявки на проблемную и не проблемную
|
||
* Если изменяется статус на проблемную, то необходимо передать текст сообщения
|
||
*
|
||
* @param {HTMLElement} button Кнопка
|
||
* @param {HTMLElement} textarea Поле для ввода описания проблемы <textarea>
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _problem(button, textarea, row) {
|
||
// Блокировка полей ввода
|
||
textarea.setAttribute("readonly", true);
|
||
|
||
// Блокировка кнопки
|
||
button.setAttribute("disabled", true);
|
||
|
||
// Деинициализация индикатора и анимации об ошибке
|
||
textarea.classList.remove("error");
|
||
|
||
// Сброс анимации
|
||
this.body.errors.classList.add("hidden");
|
||
|
||
// Запуск выполнения
|
||
this.__problem(button, textarea, row);
|
||
}
|
||
|
||
/**
|
||
* Проблемная заявка (демпфер)
|
||
*
|
||
* Изменить статус заявки на проблемную и не проблемную
|
||
* Если изменяется статус на проблемную, то необходимо передать текст сообщения
|
||
*
|
||
* @param {HTMLElement} button Кнопка
|
||
* @param {HTMLElement} textarea Поле для ввода описания проблемы <textarea>
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static __problem = damper(async (button, textarea, row) => {
|
||
if (row instanceof HTMLElement) {
|
||
// Получена строка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Инициализация функции разблокировки
|
||
function unblock() {
|
||
// Разблокировка поля ввода
|
||
textarea.removeAttribute("readonly");
|
||
|
||
// Разблокировка кнопки
|
||
button.removeAttribute("disabled");
|
||
}
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/problem`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: textarea instanceof HTMLElement && textarea.value.length > 0
|
||
? `text=${textarea.value}`
|
||
: "",
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Инициализация отображения ошибки
|
||
textarea.classList.add("error");
|
||
|
||
// Фокусировка на поле ввода
|
||
textarea.focus();
|
||
|
||
// Разблокировка полей ввода и кнопок
|
||
unblock();
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(document.getElementById("popup")) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
|
||
if (data.problematic) {
|
||
// Проблемная заявка
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.add("problematic");
|
||
} else {
|
||
// Не проблемная заявка
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.remove("problematic");
|
||
}
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Деинициализация активного окна
|
||
this.body.wrap.remove();
|
||
|
||
// Удаление статуса активной строки
|
||
row.removeAttribute("data-selected");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Подтверждение заявки (вызов демпфера)
|
||
*
|
||
* @param {HTMLElement} button Кнопка <button>
|
||
* @param {HTMLElement} rating Рейтинг <input>
|
||
* @param {HTMLElement} review Отзыв <input>
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static complete(button, rating, review, row) {
|
||
// Блокировка ползунка
|
||
rating.setAttribute("disabled", true);
|
||
|
||
// Блокировка полей ввода
|
||
review.setAttribute("readonly", true);
|
||
|
||
// Деинициализация индикатора и анимации об ошибке
|
||
rating.classList.remove("error");
|
||
review.classList.remove("error");
|
||
|
||
// Блокировка кнопки
|
||
button.setAttribute("disabled", true);
|
||
|
||
// Сброс анимации
|
||
this.body.errors.classList.add("hidden");
|
||
|
||
// Запуск выполнения
|
||
this._complete(button, rating, review, row);
|
||
}
|
||
|
||
/**
|
||
* Подтверждение заявки (демпфер)
|
||
*
|
||
* @param {HTMLElement} button Кнопка <button>
|
||
* @param {HTMLElement} rating Рейтинг <input>
|
||
* @param {HTMLElement} review Отзыв <input>
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _complete = damper(async (button, rating, review, row) => {
|
||
if (
|
||
button instanceof HTMLElement && rating instanceof HTMLElement &&
|
||
review instanceof HTMLElement && row instanceof HTMLElement
|
||
) {
|
||
// Получена кнопка, рейтинг, отзыв и строка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Инициализация функции разблокировки
|
||
function unblock() {
|
||
// Разблокировка ползунка
|
||
rating.removeAttribute("disabled");
|
||
|
||
// Разблокировка поля ввода
|
||
review.removeAttribute("readonly");
|
||
|
||
// Разблокировка кнопки
|
||
button.removeAttribute("disabled");
|
||
}
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/complete`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `rating=${rating.value}&review=${review.value}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Инициализация отображения ошибки
|
||
rating.classList.add("error");
|
||
review.classList.add("error");
|
||
|
||
// Фокусировка на поле ввода
|
||
review.focus();
|
||
|
||
if (!data.completed) {
|
||
// Не завершена заявка
|
||
|
||
// Разблокировка полей ввода и кнопок
|
||
unblock();
|
||
}
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(document.getElementById("popup")) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
|
||
if (data.completed) {
|
||
// Завершена заявка
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.add("completed");
|
||
} else {
|
||
// Не завершена заявка
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.remove("completed");
|
||
}
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Деинициализация активного окна
|
||
this.body.wrap.remove();
|
||
|
||
// Удаление статуса активной строки
|
||
row.removeAttribute("data-selected");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Скрыть
|
||
*
|
||
* @param {HTMLElement} button Кнопка
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static hide = damper(async (button, row) => {
|
||
if (row instanceof HTMLElement && button instanceof HTMLElement) {
|
||
// Получена кнопка и строка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/hide`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(document.getElementById("popup")) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
|
||
if (data.hided) {
|
||
// Скрыта заявка
|
||
|
||
// Реинициализация текста
|
||
button.innerText = "Показать";
|
||
|
||
// Реинициализация стиля
|
||
button.classList.remove("sea");
|
||
button.classList.add("grass");
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.add("hided");
|
||
} else {
|
||
// Не скрыта заявка
|
||
|
||
// Реинициализация текста
|
||
button.innerText = "Скрыть";
|
||
|
||
// Реинициализация стиля
|
||
button.classList.remove("grass");
|
||
button.classList.add("sea");
|
||
|
||
// УЛОВКА: браузер ещё не успевает перерисовать документ во время исполнения this.task(row)
|
||
row.classList.remove("hided");
|
||
}
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.popup(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Удалить
|
||
*
|
||
* @param {HTMLElement} button Кнопка
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static remove = damper(async (button, row) => {
|
||
if (row instanceof HTMLElement && button instanceof HTMLElement) {
|
||
// Получена кнопка и строка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
alert(228);
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/remove`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.deleted) {
|
||
// Удалена заявка
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Деинициализация активного окна
|
||
this.body.wrap.remove();
|
||
|
||
// Удаление статуса активной строки
|
||
row.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Удаление строки
|
||
row.remove();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Прочитать значение из базы данных
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {string} name Название
|
||
*
|
||
* @return {string} Значение
|
||
*/
|
||
static async value(row, name) {
|
||
if (row instanceof HTMLElement && typeof name === "string") {
|
||
// Получена строка и название
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/value`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `name=${name}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
return data.value;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Сгенерировать список работ и выбрать в нём ту, что записана в базе данных у заявки
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {array|null} Массив HTML-элементов <option>
|
||
*/
|
||
static async works(row) {
|
||
if (row instanceof HTMLElement) {
|
||
// Получена строка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/works`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
return data.works;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Записать тип работы
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} select HTML-элемент <select>
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static work = damper(async (row, select) => {
|
||
if (row instanceof HTMLElement && select instanceof HTMLElement) {
|
||
// Получена строка и select-элемент
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/work`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `work=${select.value}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.writed) {
|
||
// Записано новое значение в базу данных
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки в документе
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(document.getElementById("popup")) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
}
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.popup(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Записать дату
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} date Дата
|
||
* @param {HTMLElement} start Время начала
|
||
* @param {HTMLElement} end Время окончания
|
||
* @param {HTMLElement} button Кнопка отправки
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static date(row, date, start, end, button) {
|
||
// Блокировка полей ввода
|
||
date.setAttribute("readonly", true);
|
||
start.setAttribute("readonly", true);
|
||
end.setAttribute("readonly", true);
|
||
button.setAttribute("disabled", true);
|
||
|
||
// Запуск выполнения
|
||
this._date(row, date, start, end, button);
|
||
}
|
||
|
||
/**
|
||
* Записать дату
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} date Дата
|
||
* @param {HTMLElement} start Время начала
|
||
* @param {HTMLElement} end Время окончания
|
||
* @param {HTMLElement} button Кнопка отправки
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _date = damper(async (row, date, start, end, button) => {
|
||
if (
|
||
row instanceof HTMLElement && date instanceof HTMLElement &&
|
||
start instanceof HTMLElement && end instanceof HTMLElement &&
|
||
button instanceof HTMLElement
|
||
) {
|
||
// Получены строка, поле даты, поле начала заявки, поле окончания заяки и кнопка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/date`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `date=${
|
||
date.valueAsDate / 1000
|
||
}&start=${start.value}&end=${end.value}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Разблокировка ввода
|
||
date.removeAttribute("readonly");
|
||
start.removeAttribute("readonly");
|
||
end.removeAttribute("readonly");
|
||
button.removeAttribute("disabled");
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.writed) {
|
||
// Записано новое значение в базу данных
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки в документе
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(document.getElementById("popup")) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
|
||
if (this.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.popup(row);
|
||
}
|
||
} else {
|
||
// Не записано новое значение в базу данных
|
||
|
||
// Разблокировка ввода
|
||
date.removeAttribute("readonly");
|
||
start.removeAttribute("readonly");
|
||
end.removeAttribute("readonly");
|
||
button.removeAttribute("disabled");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Записать описание
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} textarea HTML-элемент <textarea>
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static description(row, textarea) {
|
||
// Сброс анимации ошибки
|
||
textarea.classList.remove("error");
|
||
|
||
// Запуск выполнения
|
||
this._description(row, textarea);
|
||
}
|
||
|
||
/**
|
||
* Записать описание (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} textarea HTML-элемент <textarea>
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _description = damper(async (row, textarea) => {
|
||
if (row instanceof HTMLElement && textarea instanceof HTMLElement) {
|
||
// Получена строка и select-элемент
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/description`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `description=${textarea.value}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Запись анимации ошибки
|
||
textarea.classList.add("error");
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.writed) {
|
||
// Записано новое значение в базу данных
|
||
|
||
// Удаление анимации ошибки
|
||
textarea.classList.remove("error");
|
||
} else {
|
||
// Не записано новое значение в базу данных
|
||
|
||
// Запись анимации ошибки
|
||
textarea.classList.add("error");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Сгенерировать HTML-элемент со списком ошибок
|
||
*
|
||
* @param {object} registry Реестр ошибок
|
||
* @param {bool} render Отобразить в окне с ошибками?
|
||
* @param {bool} clean Очистить окно с ошибками перед добавлением?
|
||
*
|
||
* @return {bool} Сгенерированы ошибки?
|
||
*/
|
||
static errors(registry, render = true, clean = true) {
|
||
// Инициализация ссылки на HTML-элемент с ошибками
|
||
const wrap = document.body.contains(this.body.errors)
|
||
? this.body.errors
|
||
: document.querySelector('[data-errors="true"]');
|
||
|
||
if (wrap instanceof HTMLElement && document.body.contains(wrap)) {
|
||
// Найден HTML-элемент с ошибками
|
||
|
||
// Перерасчёт высоты элемента
|
||
function height() {
|
||
wrap.classList.remove("hidden");
|
||
wrap.classList.remove("animation");
|
||
// Реинициализация переменной с данными о высоте HTML-элемента (16 - это padding-top + padding-bottom у div#popup > section.errors)
|
||
wrap.style.setProperty(
|
||
"--height",
|
||
wrap.offsetHeight - 16 + "px",
|
||
);
|
||
wrap.classList.add("animation");
|
||
wrap.classList.add("hidden");
|
||
}
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const list = wrap.getElementsByTagName("dl")[0];
|
||
|
||
// Удаление ошибок из прошлой генерации
|
||
if (clean) list.innerHTML = null;
|
||
|
||
for (const error in registry) {
|
||
// Генерация HTML-элементов с текстами ошибок
|
||
|
||
// Инициализация HTML-элемента текста ошибки
|
||
const samp = document.createElement("samp");
|
||
|
||
if (typeof registry[error] === "object") {
|
||
// Категория ошибок
|
||
|
||
// Проверка наличия ошибок
|
||
if (registry[error].length === 0) continue;
|
||
|
||
// Инициализация HTML-элемента-оболочки
|
||
const wrap = document.createElement("dt");
|
||
|
||
// Запись текста категории
|
||
samp.innerText = error;
|
||
|
||
// Запись HTML-элементов в список
|
||
wrap.appendChild(samp);
|
||
list.appendChild(wrap);
|
||
|
||
// Реинициализация высоты
|
||
height();
|
||
|
||
// Обработка вложенных ошибок (вход в рекурсию)
|
||
this.errors(registry[error], false);
|
||
} else {
|
||
// Текст ошибки (подразумевается)
|
||
|
||
// Инициализация HTML-элемента
|
||
const wrap = document.createElement("dd");
|
||
|
||
// Запись текста ошибки
|
||
samp.innerText = registry[error];
|
||
|
||
// Запись HTML-элемента в список
|
||
wrap.appendChild(samp);
|
||
list.appendChild(wrap);
|
||
|
||
// Реинициализация высоты
|
||
height();
|
||
}
|
||
}
|
||
|
||
if (render) {
|
||
// Запрошена отрисовка
|
||
|
||
if (
|
||
list.childElementCount !== 0
|
||
) {
|
||
// Найдены ошибки
|
||
|
||
// Сброс анимации
|
||
// УЛОВКА: таким образом не запускается анимация до взаимодействия с элементом (исправлял это в CSS, но не помню как)
|
||
wrap.classList.add("animation");
|
||
|
||
// Отображение
|
||
wrap.classList.remove("hidden");
|
||
} else {
|
||
// Не найдены ошибки
|
||
|
||
// Скрытие
|
||
wrap.classList.add("hidden");
|
||
}
|
||
}
|
||
|
||
return list.childElementCount === 0 ? false : true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Сотрудник
|
||
*/
|
||
static worker = class {
|
||
/**
|
||
* Ссылка на ядро (родительский класс)
|
||
*/
|
||
static core;
|
||
|
||
/**
|
||
* Сгенерировать всплывающее окно c данными сотрудника
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static async popup(row) {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
(typeof core === "function" &&
|
||
core.interface !== "worker")
|
||
) {
|
||
// Получена строка и аккаунт не является сотрудником
|
||
|
||
// Инициализация идентификатора сотрудника
|
||
const worker =
|
||
row.querySelector('span[data-column="worker"]').innerText;
|
||
|
||
if (worker.length > 0) {
|
||
// Инициализирован идентификатор сотрудника
|
||
|
||
// Инициализация идентификатора строки
|
||
const task = row.getAttribute("id");
|
||
|
||
if (task.length > 0) {
|
||
// Инициализирован идентификатор строки
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/worker/${worker}/read`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.worker.length > 0) {
|
||
// Получены данные (подразумевается, что сотрудник найден)
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll(
|
||
'div[data-selected="true"]',
|
||
),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.core.body.wrap = document.createElement("div");
|
||
this.core.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("window", "list", "small");
|
||
|
||
// Инициализация заголовка всплывающего окна
|
||
const title = document.createElement("h3");
|
||
title.innerText = worker;
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация буфера для записи во всплывающее окно
|
||
const list = document.createElement("div");
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons = document.createElement("div");
|
||
buttons.classList.add("row", "divided", "buttons");
|
||
|
||
// Инициализация оболочки для строки с полем ввода
|
||
const label = document.createElement("label");
|
||
|
||
// Инициализация иконки для поля ввода замены
|
||
const icon = document.createElement("i");
|
||
icon.classList.add("icon", "user");
|
||
|
||
// Инициализация поля ввода замены
|
||
const input = document.createElement("input");
|
||
input.classList.add("cloud");
|
||
input.setAttribute("placeholder", "Сотрудник");
|
||
input.setAttribute("autocomplete", "username");
|
||
input.setAttribute("autofocus", "true");
|
||
input.setAttribute("title", "Идентификатор сотрудника");
|
||
input.setAttribute("list", "popup_worker_datalist");
|
||
|
||
// Инициализация кнопки смены
|
||
const replace = document.createElement("button");
|
||
replace.classList.add("sea");
|
||
replace.innerText = "Заменить";
|
||
replace.setAttribute(
|
||
"onclick",
|
||
`tasks.worker.update(document.getElementById('${task}'), this.previousElementSibling, this)`,
|
||
);
|
||
|
||
// Инициализация списка сотружников
|
||
const datalist = document.createElement("datalist");
|
||
datalist.setAttribute("id", "popup_worker_datalist");
|
||
workers.list().then((html) => datalist.innerHTML = html);
|
||
|
||
// Инициализация кнопки удаления
|
||
const remove = document.createElement("button");
|
||
remove.classList.add("clay");
|
||
remove.innerText = "Удалить";
|
||
remove.setAttribute(
|
||
"onclick",
|
||
`tasks.worker.remove(document.getElementById('${task}'), this)`,
|
||
);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.core.body.errors = document.createElement("section");
|
||
this.core.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.core.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const errors = document.createElement("section");
|
||
errors.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener(
|
||
"keydown",
|
||
this.core.buttons,
|
||
);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Запись в документ
|
||
popup.appendChild(title);
|
||
|
||
label.appendChild(icon);
|
||
label.appendChild(input);
|
||
label.appendChild(replace);
|
||
label.appendChild(remove);
|
||
label.appendChild(datalist);
|
||
buttons.appendChild(label);
|
||
column.appendChild(list);
|
||
column.appendChild(buttons);
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.core.body.wrap.appendChild(popup);
|
||
|
||
errors.appendChild(dl);
|
||
this.core.body.errors.appendChild(errors);
|
||
this.core.body.wrap.appendChild(this.core.body.errors);
|
||
|
||
document.body.appendChild(this.core.body.wrap);
|
||
|
||
// Запись в документ HTML-данных через буфер
|
||
list.outerHTML = data.worker;
|
||
|
||
// Инициализация элемента с номером в списке данных сотрудника
|
||
const number = document.getElementById(`${worker}_number`)
|
||
.getElementsByTagName("a")[0];
|
||
|
||
// Инициализация маски номера
|
||
const mask = IMask.createMask({
|
||
mask: "+{7} (000) 000-00-00",
|
||
});
|
||
|
||
// Применение маски номера
|
||
mask.resolve(number.innerText);
|
||
|
||
// Запись нового значения
|
||
number.innerText = mask.value;
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.core.body.errors);
|
||
const resize = new ResizeObserver(() =>
|
||
top(this.core.body.errors)
|
||
);
|
||
resize.observe(this.core.body.wrap);
|
||
|
||
// Инициалиация маски идентификатора сотрудника в поле ввода идентификатора сотрудника
|
||
IMask(input, { mask: "000000000" });
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.core.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.core.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll(
|
||
'div[data-selected="true"]',
|
||
),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener(
|
||
"keydown",
|
||
this.core.buttons,
|
||
);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () =>
|
||
this.core.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.core.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (
|
||
const select of popup.getElementsByTagName("select")
|
||
) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
|
||
for (
|
||
const option of select.getElementsByTagName("option")
|
||
) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (
|
||
const textarea of popup.getElementsByTagName("textarea")
|
||
) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.core.body.errors))
|
||
.observe(textarea, {
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
});
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener(
|
||
"focus",
|
||
() => this.core.freeze = true,
|
||
);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.core.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.core.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.core.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.core.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.core.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "заменить"
|
||
replace.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.core.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.core.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
input.focus();
|
||
} else {
|
||
// Не получены данные (подразумевается, что сотрудник не найден)
|
||
|
||
// Открытие окна инициализации сотрудника
|
||
this.init(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
} else {
|
||
// Не инициализирован идентификатор сотрудника
|
||
|
||
// Открытие окна инициализации сотрудника
|
||
this.init(row);
|
||
}
|
||
}
|
||
}
|
||
|
||
static init(row) {
|
||
// Инициализация идентификатора строки
|
||
const task = row.getAttribute("id");
|
||
|
||
if (task.length > 0) {
|
||
// Инициализирован идентификатор строки
|
||
|
||
// Инициализация статуса: "опубликовано"
|
||
const published = row.classList.contains("published");
|
||
|
||
// Инициализация статуса: "подтверждено"
|
||
const confirmed = row.classList.contains("confirmed");
|
||
|
||
// Инициализация статуса: "завершено"
|
||
const completed = row.classList.contains("completed");
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.core.body.wrap = document.createElement("div");
|
||
this.core.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("window", "list", "small");
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons = document.createElement("div");
|
||
buttons.classList.add("row", "buttons");
|
||
|
||
// Инициализация оболочки для строки с полем ввода
|
||
const label = document.createElement("label");
|
||
|
||
// Инициализация иконки для поля ввода замены
|
||
const icon = document.createElement("i");
|
||
icon.classList.add("icon", "user");
|
||
|
||
// Инициализация поля ввода замены
|
||
const input = document.createElement("input");
|
||
input.classList.add("cloud");
|
||
input.setAttribute("placeholder", "Сотрудник");
|
||
input.setAttribute("autocomplete", "username");
|
||
input.setAttribute("autofocus", "true");
|
||
input.setAttribute("title", "Идентификатор сотрудника");
|
||
input.setAttribute("list", "popup_worker_datalist");
|
||
if (published || confirmed || completed) {
|
||
input.setAttribute("readonly", "true");
|
||
}
|
||
|
||
// Инициализация кнопки замены
|
||
const connect = document.createElement("button");
|
||
connect.classList.add("grass");
|
||
connect.innerText = "Назначить";
|
||
connect.setAttribute(
|
||
"onclick",
|
||
`tasks.worker.update(document.getElementById('${task}'), this.previousElementSibling, this)`,
|
||
);
|
||
if (published || confirmed || completed) {
|
||
connect.setAttribute("disabled", "true");
|
||
}
|
||
|
||
// Инициализация списка сотрудников
|
||
const datalist = document.createElement("datalist");
|
||
datalist.setAttribute("id", "popup_worker_datalist");
|
||
workers.list().then((html) => datalist.innerHTML = html);
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons_2 = document.createElement("div");
|
||
buttons_2.classList.add("row", "buttons");
|
||
|
||
// Инициализация оболочки для строки с полем ввода
|
||
const label_2 = document.createElement("label");
|
||
|
||
// Инициализация кнопки замены
|
||
const publish = document.createElement("button");
|
||
publish.classList.add(published ? "clay" : "sea");
|
||
publish.innerText = published ? "Снять с публикации" : "Опубликовать";
|
||
publish.setAttribute(
|
||
"onclick",
|
||
published
|
||
? `tasks.worker.unpublish(document.getElementById('${task}'), this)`
|
||
: `tasks.worker.publish(document.getElementById('${task}'), this)`,
|
||
);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.core.body.errors = document.createElement("section");
|
||
this.core.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.core.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const body = document.createElement("section");
|
||
body.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Запись в документ
|
||
label.appendChild(icon);
|
||
label.appendChild(input);
|
||
label.appendChild(connect);
|
||
label.appendChild(datalist);
|
||
buttons.appendChild(label);
|
||
column.appendChild(buttons);
|
||
|
||
if (
|
||
typeof core === "function" &&
|
||
core.interface !== "market" &&
|
||
core.interface !== "worker"
|
||
) {
|
||
// Скрытие для магазина и сотрудника
|
||
|
||
label_2.appendChild(publish);
|
||
buttons_2.appendChild(label_2);
|
||
column.appendChild(buttons_2);
|
||
}
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.core.body.wrap.appendChild(popup);
|
||
|
||
body.appendChild(dl);
|
||
this.core.body.errors.appendChild(body);
|
||
this.core.body.wrap.appendChild(this.core.body.errors);
|
||
|
||
document.body.appendChild(this.core.body.wrap);
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.core.body.errors);
|
||
const resize = new ResizeObserver(() => top(this.core.body.errors));
|
||
resize.observe(this.core.body.wrap);
|
||
|
||
// Инициалиация маски идентификатора сотрудника
|
||
IMask(input, { mask: "000000000" });
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.core.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.core.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () =>
|
||
this.core.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.core.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (const select of popup.getElementsByTagName("select")) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
|
||
for (
|
||
const option of select.getElementsByTagName("option")
|
||
) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (
|
||
const textarea of popup.getElementsByTagName("textarea")
|
||
) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.core.body.errors)).observe(
|
||
textarea,
|
||
{
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
},
|
||
);
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener(
|
||
"focus",
|
||
() => this.core.freeze = true,
|
||
);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.core.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.core.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.core.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.core.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.core.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "назначить"
|
||
connect.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.core.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.core.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
input.focus();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновить
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} input Поле для ввода идентификатора сотрудника
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static update(row, input, button) {
|
||
// Блокировка поля ввода
|
||
input.setAttribute("readonly", true);
|
||
button.setAttribute("disabled", true);
|
||
|
||
// Деинициализация индикатора и анимации об ошибке
|
||
input.classList.remove("error");
|
||
input.previousElementSibling.classList.remove("error");
|
||
|
||
// Сброс анимации
|
||
this.core.body.errors.classList.add("hidden");
|
||
|
||
// Запуск выполнения
|
||
this._update(row, input, button);
|
||
}
|
||
|
||
/**
|
||
* Обновить (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} input Поле для ввода идентификатора сотрудника
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _update = damper(async (row, input, button) => {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
input instanceof HTMLElement &&
|
||
button instanceof HTMLElement
|
||
) {
|
||
// Получена строка, поле для ввода и кнопка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Инициализация функции разблокировки
|
||
function unblock() {
|
||
// Разблокировка поля ввода
|
||
input.removeAttribute("readonly");
|
||
button.removeAttribute("disabled");
|
||
}
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.core.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/worker/update`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `worker=${input.value}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Инициализация отображения ошибки
|
||
input.classList.add("error");
|
||
input.previousElementSibling.classList.add("error");
|
||
|
||
// Фокусировка на поле ввода
|
||
input.focus();
|
||
|
||
// Разблокировка полей ввода и кнопок
|
||
unblock();
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.updated) {
|
||
// Записано обновление
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
}
|
||
|
||
if (this.core.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.worker.popup(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Удалить
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static remove(row, button) {
|
||
// Запуск выполнения
|
||
this._remove(row, button);
|
||
}
|
||
|
||
/**
|
||
* Удалить (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _remove = damper(async (row, button) => {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
button instanceof HTMLElement
|
||
) {
|
||
// Получена строка и кнопка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.core.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/worker/update`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `worker=delete`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.updated) {
|
||
// Записано обновление
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
}
|
||
|
||
if (this.core.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.worker.popup(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Опубликовать
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static publish(row, button) {
|
||
// Запуск выполнения
|
||
this._publish(row, button);
|
||
}
|
||
|
||
/**
|
||
* Опубликовать (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _publish = damper(async (row, button) => {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
button instanceof HTMLElement
|
||
) {
|
||
// Получена строка и кнопка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.core.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/publish`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.published) {
|
||
// Опубликована заявка
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(id);
|
||
|
||
// Копирование классов из буфера классов удалённой строки и добавление соответствующего
|
||
// row.classList.add(...buffer, "published");
|
||
row.classList.add("published");
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
|
||
// Запись текста состояния кнопки
|
||
button.innerText = "Снять с публикации";
|
||
|
||
// Реинициализация стиля кнопки
|
||
button.classList.remove("sea");
|
||
button.classList.add("clay");
|
||
|
||
// Инициализация вызова функции: "снять с публикации"
|
||
button.setAttribute(
|
||
"onclick",
|
||
`tasks.worker.unpublish(document.getElementById('${id}'), this)`,
|
||
);
|
||
|
||
// Инициализация оболочки элементов для блокировки
|
||
const wrap =
|
||
button.parentElement.parentElement.previousElementSibling
|
||
.children[0];
|
||
|
||
// Блокировка поля для ввода сотрудника
|
||
wrap.getElementsByTagName("input")[0].setAttribute(
|
||
"readonly",
|
||
"true",
|
||
);
|
||
|
||
// Блокировка кнопки назначения сотрудника
|
||
wrap.getElementsByTagName("button")[0].setAttribute(
|
||
"disabled",
|
||
"true",
|
||
);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Снять с публикации
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static unpublish(row, button) {
|
||
// Запуск выполнения
|
||
this._unpublish(row, button);
|
||
}
|
||
|
||
/**
|
||
* Снять с публикации (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _unpublish = damper(async (row, button) => {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
button instanceof HTMLElement
|
||
) {
|
||
// Получена строка и кнопка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.core.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/unpublish`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.unpublished) {
|
||
// Снята с публикации заявка
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки и добавление соответствующего
|
||
// row.classList.add(...buffer, "published");
|
||
// row.classList.add("published");
|
||
|
||
// Удаление класса неактуального состояния
|
||
// row.classList.remove("published");
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
|
||
// Запись текста состояния кнопки
|
||
button.innerText = "Опубликовать";
|
||
|
||
// Реинициализация стиля кнопки
|
||
button.classList.remove("clay");
|
||
button.classList.add("sea");
|
||
|
||
// Инициализация вызова функции: "опубликовать"
|
||
button.setAttribute(
|
||
"onclick",
|
||
`tasks.worker.publish(document.getElementById('${id}'), this)`,
|
||
);
|
||
|
||
// Инициализация оболочки элементов для разблокировки
|
||
const wrap =
|
||
button.parentElement.parentElement.previousElementSibling
|
||
.children[0];
|
||
|
||
// Разблокировка поля для ввода сотрудника
|
||
wrap.getElementsByTagName("input")[0].removeAttribute(
|
||
"readonly",
|
||
);
|
||
|
||
// Разблокировка кнопки назначения сотрудника
|
||
wrap.getElementsByTagName("button")[0].removeAttribute(
|
||
"disabled",
|
||
);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
};
|
||
|
||
/**
|
||
* Магазин
|
||
*/
|
||
static market = class {
|
||
/**
|
||
* Ссылка на ядро (родительский класс)
|
||
*/
|
||
static core;
|
||
|
||
/**
|
||
* Сгенерировать всплывающее окно c данными магазина
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static async popup(row) {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
(typeof core === "function" &&
|
||
core.interface !== "market" &&
|
||
core.interface !== "worker")
|
||
) {
|
||
// Получена строка и аккаунт не является магазином или сотрудником
|
||
|
||
// Инициализация идентификатора магазина
|
||
const market =
|
||
row.querySelector('span[data-column="market"]').innerText;
|
||
|
||
if (market.length > 0) {
|
||
// Инициализирован идентификатор магазина
|
||
|
||
// Инициализация идентификатора строки
|
||
const task = row.getAttribute("id");
|
||
|
||
if (task.length > 0) {
|
||
// Инициализирован идентификатор строки
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/market/${market}/read`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.market.length > 0) {
|
||
// Получены данные (подразумевается, что сотрудник найден)
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll(
|
||
'div[data-selected="true"]',
|
||
),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.core.body.wrap = document.createElement("div");
|
||
this.core.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("window", "list", "small");
|
||
|
||
// Инициализация заголовка всплывающего окна
|
||
const title = document.createElement("h3");
|
||
title.classList.add("unselectable");
|
||
title.innerText = market;
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация буфера для записи во всплывающее окно
|
||
const list = document.createElement("div");
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons = document.createElement("div");
|
||
buttons.classList.add("row", "buttons");
|
||
|
||
// Инициализация оболочки для строки с полем ввода
|
||
const label = document.createElement("label");
|
||
|
||
// Инициализация иконки для поля ввода замены
|
||
const icon = document.createElement("i");
|
||
icon.classList.add("icon", "shopping", "cart");
|
||
|
||
// Инициализация поля ввода замены
|
||
const input = document.createElement("input");
|
||
input.classList.add("cloud");
|
||
input.setAttribute("placeholder", "Магазин");
|
||
input.setAttribute("autocomplete", "username");
|
||
input.setAttribute("autofocus", "true");
|
||
input.setAttribute("title", "Идентификатор магазина");
|
||
input.setAttribute("list", "popup_market_datalist");
|
||
|
||
// Инициализация кнопки смены
|
||
const replace = document.createElement("button");
|
||
replace.classList.add("sea");
|
||
replace.innerText = "Заменить";
|
||
replace.setAttribute(
|
||
"onclick",
|
||
`tasks.market.update(document.getElementById('${task}'), this.previousElementSibling, this)`,
|
||
);
|
||
|
||
// Инициализация списка сотружников
|
||
const datalist = document.createElement("datalist");
|
||
datalist.setAttribute("id", "popup_market_datalist");
|
||
markets.list().then((html) => datalist.innerHTML = html);
|
||
|
||
// Инициализация кнопки удаления
|
||
const remove = document.createElement("button");
|
||
remove.classList.add("clay");
|
||
remove.innerText = "Удалить";
|
||
remove.setAttribute(
|
||
"onclick",
|
||
`tasks.market.remove(document.getElementById('${task}'), this)`,
|
||
);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.core.body.errors = document.createElement("section");
|
||
this.core.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.core.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const body = document.createElement("section");
|
||
body.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener(
|
||
"keydown",
|
||
this.core.buttons,
|
||
);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Запись в документ
|
||
popup.appendChild(title);
|
||
|
||
label.appendChild(icon);
|
||
label.appendChild(input);
|
||
label.appendChild(replace);
|
||
label.appendChild(remove);
|
||
label.appendChild(datalist);
|
||
buttons.appendChild(label);
|
||
column.appendChild(list);
|
||
column.appendChild(buttons);
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.core.body.wrap.appendChild(popup);
|
||
|
||
body.appendChild(dl);
|
||
this.core.body.errors.appendChild(body);
|
||
this.core.body.wrap.appendChild(this.core.body.errors);
|
||
|
||
document.body.appendChild(this.core.body.wrap);
|
||
|
||
// Запись в документ HTML-данных через буфер
|
||
list.outerHTML = data.market;
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.core.body.errors);
|
||
const resize = new ResizeObserver(() =>
|
||
top(this.core.body.errors)
|
||
);
|
||
resize.observe(this.core.body.wrap);
|
||
|
||
// Инициалиация маски идентификатора магазина в поле ввода идентификатора магазина
|
||
IMask(input, { mask: "000000000" });
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.core.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.core.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll(
|
||
'div[data-selected="true"]',
|
||
),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener(
|
||
"keydown",
|
||
this.core.buttons,
|
||
);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () =>
|
||
this.core.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.core.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (
|
||
const select of popup.getElementsByTagName("select")
|
||
) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
|
||
for (
|
||
const option of select.getElementsByTagName("option")
|
||
) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (
|
||
const textarea of popup.getElementsByTagName("textarea")
|
||
) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.core.body.errors))
|
||
.observe(textarea, {
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
});
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener(
|
||
"focus",
|
||
() => this.core.freeze = true,
|
||
);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.core.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.core.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.core.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.core.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.core.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "заменить"
|
||
replace.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.core.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.core.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
input.focus();
|
||
} else {
|
||
// Не получены данные (подразумевается, что магазин не найден)
|
||
|
||
// Открытие окна инициализации магазина
|
||
this.init(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
} else {
|
||
// Не инициализирован идентификатор магазина
|
||
|
||
// Открытие окна инициализации магазина
|
||
this.init(row);
|
||
}
|
||
}
|
||
}
|
||
|
||
static init(row) {
|
||
// Инициализация идентификатора строки
|
||
const task = row.getAttribute("id");
|
||
|
||
if (task.length > 0) {
|
||
// Инициализирован идентификатор строки
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (Магазин обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.core.body.wrap = document.createElement("div");
|
||
this.core.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("window", "list", "small");
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация оболочки для кнопок
|
||
const buttons = document.createElement("div");
|
||
buttons.classList.add("row", "buttons");
|
||
|
||
// Инициализация оболочки для строки с полем ввода
|
||
const label = document.createElement("label");
|
||
|
||
// Инициализация иконки для поля ввода замены
|
||
const icon = document.createElement("i");
|
||
icon.classList.add("icon", "shopping", "cart");
|
||
|
||
// Инициализация поля ввода замены
|
||
const input = document.createElement("input");
|
||
input.classList.add("cloud");
|
||
input.setAttribute("placeholder", "Магазин");
|
||
input.setAttribute("autocomplete", "username");
|
||
input.setAttribute("autofocus", "true");
|
||
input.setAttribute("title", "Идентификатор магазина");
|
||
input.setAttribute("list", "popup_market_datalist");
|
||
|
||
// Инициализация кнопки замены
|
||
const connect = document.createElement("button");
|
||
connect.classList.add("grass");
|
||
connect.innerText = "Назначить";
|
||
connect.setAttribute(
|
||
"onclick",
|
||
`tasks.market.update(document.getElementById('${task}'), this.previousElementSibling, this)`,
|
||
);
|
||
|
||
// Инициализация списка сотружников
|
||
const datalist = document.createElement("datalist");
|
||
datalist.setAttribute("id", "popup_market_datalist");
|
||
markets.list().then((html) => datalist.innerHTML = html);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.core.body.errors = document.createElement("section");
|
||
this.core.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.core.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const body = document.createElement("section");
|
||
body.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Запись в документ
|
||
label.appendChild(icon);
|
||
label.appendChild(input);
|
||
label.appendChild(connect);
|
||
label.appendChild(datalist);
|
||
buttons.appendChild(label);
|
||
column.appendChild(buttons);
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.core.body.wrap.appendChild(popup);
|
||
|
||
body.appendChild(dl);
|
||
this.core.body.errors.appendChild(body);
|
||
this.core.body.wrap.appendChild(this.core.body.errors);
|
||
|
||
document.body.appendChild(this.core.body.wrap);
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.core.body.errors);
|
||
const resize = new ResizeObserver(() => top(this.core.body.errors));
|
||
resize.observe(this.core.body.wrap);
|
||
|
||
// Инициалиация маски идентификатора магазина
|
||
IMask(input, { mask: "000000000" });
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.core.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.core.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () =>
|
||
this.core.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.core.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (const select of popup.getElementsByTagName("select")) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
|
||
for (const option of select.getElementsByTagName("option")) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (const textarea of popup.getElementsByTagName("textarea")) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.core.body.errors)).observe(
|
||
textarea,
|
||
{
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
},
|
||
);
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener(
|
||
"focus",
|
||
() => this.core.freeze = true,
|
||
);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.core.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.core.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.core.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
} else if (event.keyCode === 13) {
|
||
// Нажата кнопка: "enter"
|
||
|
||
// Инициализация буфера с текущим статусом блокировки закрытия окна
|
||
const freeze = this.core.freeze;
|
||
|
||
// Блокировка закрытия окна (чтобы не вызвался click() через событие onclick)
|
||
this.core.freeze = true;
|
||
|
||
// Активация виртуальной кнопки "назначить"
|
||
connect.click();
|
||
|
||
// Возвращение статуса блокировки закрытия окна
|
||
this.core.freeze = freeze;
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.core.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
input.focus();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Обновить
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} input Поле для ввода идентификатора магазина
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static update(row, input, button) {
|
||
// Блокировка поля ввода
|
||
input.setAttribute("readonly", true);
|
||
button.setAttribute("disabled", true);
|
||
|
||
// Деинициализация индикатора и анимации об ошибке
|
||
input.classList.remove("error");
|
||
input.previousElementSibling.classList.remove("error");
|
||
|
||
// Сброс анимации
|
||
this.core.body.errors.classList.add("hidden");
|
||
|
||
// Запуск выполнения
|
||
this._update(row, input, button);
|
||
}
|
||
|
||
/**
|
||
* Обновить (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} input Поле для ввода идентификатора магазина
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _update = damper(async (row, input, button) => {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
input instanceof HTMLElement &&
|
||
button instanceof HTMLElement
|
||
) {
|
||
// Получена строка, поле для ввода и кнопка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Инициализация функции разблокировки
|
||
function unblock() {
|
||
// Разблокировка поля ввода
|
||
input.removeAttribute("readonly");
|
||
button.removeAttribute("disabled");
|
||
}
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.core.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/market/update`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `market=${input.value}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Инициализация отображения ошибки
|
||
input.classList.add("error");
|
||
input.previousElementSibling.classList.add("error");
|
||
|
||
// Фокусировка на поле ввода
|
||
input.focus();
|
||
|
||
// Разблокировка полей ввода и кнопок
|
||
unblock();
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.updated) {
|
||
// Записано обновление
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
}
|
||
|
||
if (this.core.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.market.popup(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Удалить
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static remove(row, button) {
|
||
// Запуск выполнения
|
||
this._remove(row, button);
|
||
}
|
||
|
||
/**
|
||
* Удалить (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} button Кнопка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _remove = damper(async (row, button) => {
|
||
if (row instanceof HTMLElement && button instanceof HTMLElement) {
|
||
// Получена строка и кнопка
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запуск отсрочки разблокировки на случай, если сервер не отвечает
|
||
const timeout = setTimeout(() => {
|
||
this.core.errors(["Сервер не отвечает"]);
|
||
unblock();
|
||
}, 5000);
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/market/update`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `market=delete`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
// Удаление отсрочки разблокировки
|
||
clearTimeout(timeout);
|
||
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Разблокировка полей ввода и кнопок
|
||
unblock();
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.updated) {
|
||
// Записано обновление
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
}
|
||
|
||
if (this.core.body.wrap instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Реинициализация активного окна
|
||
tasks.market.popup(row);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
};
|
||
|
||
/**
|
||
* Комментарий
|
||
*/
|
||
static commentary = class {
|
||
/**
|
||
* Ссылка на ядро (родительский класс)
|
||
*/
|
||
static core;
|
||
|
||
/**
|
||
* Сгенерировать всплывающее окно c комментарием задачи
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static popup = damper((row) => {
|
||
if (
|
||
row instanceof HTMLElement &&
|
||
(typeof core === "function" &&
|
||
core.interface !== "market" &&
|
||
core.interface !== "worker")
|
||
) {
|
||
// Получена строка и аккаунт не является магазином или сотрудником
|
||
|
||
// Инициализация идентификатора сотрудника
|
||
const task = row.getAttribute("id");
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Инициализация статуса активной строки (обрабатываемой в данный момент)
|
||
row.setAttribute("data-selected", "true");
|
||
|
||
// Инициализация оболочки всплывающего окна
|
||
this.core.body.wrap = document.createElement("div");
|
||
this.core.body.wrap.setAttribute("id", "popup");
|
||
|
||
// Инициализация всплывающего окна
|
||
const popup = document.createElement("section");
|
||
popup.classList.add("list", "small");
|
||
|
||
// Инициализация заголовка всплывающего окна
|
||
const title = document.createElement("h3");
|
||
title.innerText = "Комментарий";
|
||
|
||
// Инициализация оболочки с основной информацией
|
||
const main = document.createElement("section");
|
||
main.classList.add("main");
|
||
|
||
// Инициализация колонки
|
||
const column = document.createElement("div");
|
||
column.classList.add("column");
|
||
|
||
// Инициализация строки
|
||
const row_1 = document.createElement("div");
|
||
row_1.classList.add("row", "stretchable");
|
||
|
||
// Инициализация оболочки для поля ввода комментария
|
||
const label = document.createElement("label");
|
||
|
||
// Инициализация поля ввода комментария
|
||
const textarea = document.createElement("textarea");
|
||
textarea.classList.add("snow");
|
||
this.core.value(row, "commentary").then((text) =>
|
||
textarea.value = text
|
||
);
|
||
textarea.setAttribute("autofocus", "true");
|
||
textarea.setAttribute("maxlength", "800");
|
||
textarea.setAttribute(
|
||
"onkeyup",
|
||
`tasks.commentary.update(document.getElementById('${task}'), this)`,
|
||
);
|
||
textarea.setAttribute("title", "Дополнительная информация");
|
||
textarea.setAttribute(
|
||
"placeholder",
|
||
"Дополнительная информация о заявке",
|
||
);
|
||
|
||
// Инициализация окна с ошибками
|
||
this.core.body.errors = document.createElement("section");
|
||
this.core.body.errors.classList.add(
|
||
"errors",
|
||
"window",
|
||
"list",
|
||
"small",
|
||
"hidden",
|
||
);
|
||
this.core.body.errors.setAttribute("data-errors", true);
|
||
|
||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||
const errors = document.createElement("section");
|
||
errors.classList.add("body");
|
||
|
||
// Инициализация элемента-списка ошибок
|
||
const dl = document.createElement("dl");
|
||
|
||
// Инициализация активного всплывающего окна
|
||
const old = document.getElementById("popup");
|
||
|
||
if (old instanceof HTMLElement) {
|
||
// Найдено активное окно
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
|
||
// Удаление активного окна
|
||
old.remove();
|
||
}
|
||
|
||
// Удаление блокировки выполнения закрытия всплывающего окна
|
||
this.core.freeze = false;
|
||
|
||
// Запись в документ
|
||
popup.appendChild(title);
|
||
|
||
label.appendChild(textarea);
|
||
row_1.appendChild(label);
|
||
column.appendChild(row_1);
|
||
|
||
main.appendChild(column);
|
||
popup.appendChild(main);
|
||
this.core.body.wrap.appendChild(popup);
|
||
|
||
errors.appendChild(dl);
|
||
this.core.body.errors.appendChild(errors);
|
||
this.core.body.wrap.appendChild(this.core.body.errors);
|
||
|
||
document.body.appendChild(this.core.body.wrap);
|
||
|
||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||
function top(errors) {
|
||
errors.style.setProperty(
|
||
"transition",
|
||
"0s",
|
||
);
|
||
errors.style.setProperty(
|
||
"--top",
|
||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||
);
|
||
setTimeout(
|
||
() => errors.style.removeProperty("transition"),
|
||
100,
|
||
);
|
||
}
|
||
top(this.core.body.errors);
|
||
const resize = new ResizeObserver(() => top(this.core.body.errors));
|
||
resize.observe(this.core.body.wrap);
|
||
|
||
// Инициализация функции закрытия всплывающего окна
|
||
const click = () => {
|
||
// Блокировка
|
||
if (this.core.freeze) return;
|
||
|
||
// Удаление всплывающего окна
|
||
this.core.body.wrap.remove();
|
||
|
||
// Инициализация буфера выбранных строк
|
||
const buffer = [
|
||
...document.querySelectorAll('div[data-selected="true"]'),
|
||
];
|
||
|
||
// Удаление статуса активной строки у остальных строк
|
||
for (const element of buffer) {
|
||
element.removeAttribute("data-selected");
|
||
}
|
||
|
||
// Деинициализация быстрых действий по кнопкам
|
||
document.removeEventListener("keydown", this.core.buttons);
|
||
|
||
// Сброс блокировки
|
||
this.core.freeze = false;
|
||
};
|
||
|
||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||
const enable = () =>
|
||
this.core.body.wrap.addEventListener("click", click);
|
||
|
||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||
const disable = () =>
|
||
this.core.body.wrap.removeEventListener("click", click);
|
||
|
||
// Первичная активация функции удаления всплывающего окна
|
||
enable();
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с select-элементом
|
||
for (const select of popup.getElementsByTagName("select")) {
|
||
// Перебор всех select-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
select.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
|
||
for (const option of select.getElementsByTagName("option")) {
|
||
// Перебор всех option-элементов
|
||
|
||
// Инициализация функции блокировки удаления окна по событию
|
||
option.addEventListener("click", () => {
|
||
// Блокировка удаления окна
|
||
this.core.freeze = true;
|
||
|
||
// Разблокировка удаления окна
|
||
setTimeout(() => this.core.freeze = false, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Инициализация блокировки удаления окна при взаимодействии с textarea-элементом
|
||
for (const textarea of popup.getElementsByTagName("textarea")) {
|
||
// Перебор всех textarea-элементов
|
||
|
||
// Обновлять позицию окна с ошибками при изменении размера textarea (там position: absolute)
|
||
new MutationObserver(() => top(this.core.body.errors)).observe(
|
||
textarea,
|
||
{
|
||
attributes: true,
|
||
attributeFilter: ["style"],
|
||
},
|
||
);
|
||
|
||
// Инициализация функции игнорирования блокировки для выбранных кнопок
|
||
textarea.addEventListener("keydown", (e) => {
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
} else if (e.ctrlKey && e.keyCode === 13) {
|
||
// Нажаты кнопки: "control", "enter"
|
||
|
||
// Вызов глобальной функции управления кнопками
|
||
this.core.buttons(e, true);
|
||
}
|
||
});
|
||
|
||
// Добавление функции блокировки удаления окна и клавиш по событиям
|
||
textarea.addEventListener("focus", () => this.core.freeze = true);
|
||
textarea.addEventListener(
|
||
"focusout",
|
||
() => this.core.freeze = false,
|
||
);
|
||
}
|
||
|
||
// Инициализация функции управления кнопками
|
||
this.core.buttons = (e, force = false) => {
|
||
// Блокировка
|
||
if (!force && this.core.freeze) return;
|
||
|
||
if (e.keyCode === 27) {
|
||
// Нажата кнопка: "escape"
|
||
|
||
// Удаление окна
|
||
click();
|
||
}
|
||
};
|
||
|
||
// Инициализация быстрых действий по кнопкам
|
||
document.addEventListener("keydown", this.core.buttons);
|
||
|
||
// Добавление функции удаления всплывающего окна по событиям
|
||
popup.addEventListener("mouseenter", disable);
|
||
popup.addEventListener("mouseleave", enable);
|
||
|
||
// Фокусировка
|
||
textarea.focus();
|
||
}
|
||
}, 300);
|
||
|
||
/**
|
||
* Записать обновление
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} textarea HTML-элемент <textarea>
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static update(row, textarea) {
|
||
// Сброс анимации ошибки
|
||
textarea.classList.remove("error");
|
||
|
||
// Запуск выполнения
|
||
this._update(row, textarea);
|
||
}
|
||
|
||
/**
|
||
* Записать обновление (системное)
|
||
*
|
||
* @param {HTMLElement} row Строка
|
||
* @param {HTMLElement} textarea HTML-элемент <textarea>
|
||
*
|
||
* @return {void}
|
||
*/
|
||
static _update = damper(async (row, textarea) => {
|
||
if (row instanceof HTMLElement && textarea instanceof HTMLElement) {
|
||
// Получена строка и select-элемент
|
||
|
||
// Инициализация идентификатора строки
|
||
const id = row.getAttribute("id");
|
||
|
||
if (typeof id === "string") {
|
||
// Инициализирован идентификатор
|
||
|
||
// Запрос к серверу
|
||
return await fetch(`/task/${id}/commentary`, {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `commentary=${textarea.value}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.core.errors(data.errors)) {
|
||
// Сгенерированы ошибки
|
||
|
||
// Запись анимации ошибки
|
||
textarea.classList.add("error");
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
if (data.writed) {
|
||
// Записано новое значение в базу данных
|
||
|
||
// Удаление анимации ошибки
|
||
textarea.classList.remove("error");
|
||
|
||
// Инициализация буфера списка классов
|
||
// const buffer = [...row.classList];
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.row;
|
||
|
||
// Реинициализация строки (выражение странное, но правильное)
|
||
row = document.getElementById(row.getAttribute("id"));
|
||
|
||
// Копирование классов из буфера классов удалённой строки
|
||
// row.classList.add(...buffer);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
) &&
|
||
!document.body.contains(
|
||
document.querySelector('[data-selected="true"]'),
|
||
)
|
||
) {
|
||
row.setAttribute("data-selected", "true");
|
||
}
|
||
} else {
|
||
// Не записано новое значение в базу данных
|
||
|
||
// Запись анимации ошибки
|
||
textarea.classList.add("error");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}, 300);
|
||
};
|
||
|
||
static reinitializer = class {
|
||
/**
|
||
* Ссылка на ядро (родительский класс)
|
||
*/
|
||
static core;
|
||
|
||
/**
|
||
* Запустить
|
||
*
|
||
* @param {HTMLElement|null} target Строка
|
||
*/
|
||
static start(target) {
|
||
// Инициализация оболочки
|
||
const tasks = document.getElementById("tasks");
|
||
|
||
for (
|
||
let row of typeof target === "undefined"
|
||
? tasks.querySelectorAll(':scope > div[data-row="task"]')
|
||
: [target]
|
||
) {
|
||
// Перебор строк
|
||
|
||
// Инициализация номера итерации по умолчанию
|
||
if (typeof row.counter === "undefined") {
|
||
// Не инициализирован счётчик итерации
|
||
|
||
row.counter = Math.floor(Math.random() * 280 + 20);
|
||
row.setAttribute("data-counter", row.counter);
|
||
}
|
||
|
||
// Инициализация самообновления
|
||
if (typeof row.updater === "undefined") {
|
||
// Не инициализирована функция самообновления
|
||
|
||
row.updater = setInterval(
|
||
() => {
|
||
if (--row.counter <= 0) {
|
||
// Отсчёт таймера завершён
|
||
|
||
// Блокировка строки
|
||
|
||
// Деинициализация реинициализатора строки (для защиты от повторного запроса)
|
||
this.stop(row);
|
||
|
||
// Запись анимации (запуск)
|
||
row.classList.add("reinitialized");
|
||
|
||
// Запрос к серверу
|
||
fetch("/tasks/read", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/x-www-form-urlencoded",
|
||
},
|
||
body: `row=${row.getAttribute("id")}`,
|
||
})
|
||
.then((response) => response.json())
|
||
.then((data) => {
|
||
if (this.core.errors(data.errors, false, false)) {
|
||
// Сгенерированы ошибки
|
||
} else {
|
||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
||
|
||
// Инициализация идентифиатора
|
||
const id = row.getAttribute("id");
|
||
|
||
// Инициализация количества непрочитанных сообщений
|
||
const messages = row.lastElementChild.innerText;
|
||
|
||
// Инициализация статуса активной строки
|
||
const selected = row.getAttribute("data-selected");
|
||
|
||
// Реинициализация строки
|
||
row.outerHTML = data.rows;
|
||
|
||
// Реинициализация перезаписанной строки
|
||
row = document.getElementById(id);
|
||
|
||
// Копирование статуса активной строки
|
||
if (
|
||
typeof selected === "string" && selected === "true" &&
|
||
document.body.contains(
|
||
document.getElementById("popup"),
|
||
)
|
||
) row.setAttribute("data-selected", "true");
|
||
|
||
// Если непрочитанных сообщений стало больше, то проиграть звук уведомления
|
||
if (row.lastElementChild.innerText > messages) {
|
||
window.notification.play();
|
||
}
|
||
|
||
// Инициализация реинициализатора строки (для пересчёта таймера для setInterval)
|
||
if (row instanceof HTMLElement) {
|
||
setTimeout(() => this.start(row), 5000);
|
||
}
|
||
}
|
||
});
|
||
} else row.setAttribute("data-counter", row.counter);
|
||
},
|
||
Math.floor(
|
||
Math.random() * tasks.querySelectorAll(
|
||
':scope > div[data-row="task"]',
|
||
).length *
|
||
200 + 300,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Остановить
|
||
*
|
||
* @param {HTMLElement|null} target Строка
|
||
*/
|
||
static stop(target) {
|
||
// Инициализация оболочки
|
||
const tasks = document.getElementById("tasks");
|
||
|
||
for (
|
||
const row of typeof target === "undefined"
|
||
? tasks.querySelectorAll(':scope > div[data-row="task"]')
|
||
: [target]
|
||
) {
|
||
// Перебор строк
|
||
|
||
// Деинициализация самообновления
|
||
clearInterval(row.updater);
|
||
|
||
// Удаление идентификатора
|
||
row.updater = row.counter = undefined;
|
||
|
||
// Удаление анимации (сброс)
|
||
row.classList.remove("reinitialized");
|
||
}
|
||
}
|
||
};
|
||
};
|
||
|
||
// Инициализация ссылок на ядро
|
||
window.tasks.worker.core =
|
||
window.tasks.market.core =
|
||
window.tasks.commentary.core =
|
||
window.tasks.reinitializer.core =
|
||
window.tasks;
|
||
}
|
||
|
||
// Вызов события: "инициализировано"
|
||
document.dispatchEvent(
|
||
new CustomEvent("tasks.initialized", {
|
||
detail: { tasks: window.tasks },
|
||
}),
|
||
);
|