Устранение наложений временных интервалов при множественном выборе слотов (мультивремя), вывод последовательности ровно стыкуемых слотов
Для некоторых услуг может потребоваться установить интервал для записи меньше, чем длительность услуги (одного слота). Например, при аренде студии клиент может выбрать подряд несколько часов, а между разными клиентами требуется перерыв на подготовку помещения и оборудования; но перерыв длиной в час был бы избыточным, тогда устанавливается интервал для записи = 15 минут (вместо 60, которым равен один выбираемый клиентом слот).
Тогда для того, чтобы клиент не мог отметить одновременно, к примеру, слот "16:00 - 17:00" и "16:15 - 17:15", Вы можете добавить в свой виджет онлайн-записи данную надстройку, чтобы после выбора, например, слота "16:00 - 17:00" - все накладывающиеся на него промежутки (с 16:15, 16:30, 16:45) скрывались, и следующим оставался только ровно пристыкуемый слот с 17:00 и т. д.
В приведенном ниже коде требуется только прописать вводные данные (правила) - в какой услуге сколько раз интервал для записи укладывается в ее длительность.
Фрагмент, в который необходимо внести изменения, совсем компактный и не требует навыков программирования (при возникновении каких-либо затруднений Вы можете обратиться в нашу службу поддержки):

Наглядный пример настройки
Приведем пример конструкции с единственным правилом, содержащим все возможные переменные (при этом overlapScore - обязательная):
var INTERVAL_OFFSET_RULES = [
{ overlapScore: 4, serviceIDs: ["11111", "22222"], aroundTheClock: true}
];
- правило соответствует двум услугам почасовой (длительность = 60 минут) круглосуточной аренды студии с интервалом для записи = 15 минут (overlapScore = 60 / 15 = 4)
Назначение переменных
- overlapScore - число, допустим N, во сколько раз длительность слота превышает интервал для записи, или "оставить видимым только каждый N-й слот".
Примечание: услугу следует настраивать так, чтобы интервал для записи ровно укладывался в длительность целое количество раз.Обязательная переменная, содержать ее должно любое правило (если не заданы serviceIDs, правило будет применяться ко всем услугам).
- serviceIDs - ID услуг, к которым применяется правило. Заключены в квадратные скобки, прописываются в кавычках через запятую, после последнего или единственного ID запятая не ставится. Если serviceIDs отсутствует, правило применяется ко всем услугам.
- aroundTheClock - true / false, признак круглосуточности услуги. По умолчанию aroundTheClock: false (последовательность стыкуемых слотов выстраивается только до конца рабочего дня), и в этом случае ее прописывать строго не требуется (можно опустить), но для наглядности - не помешает.
Если же aroundTheClock: true, то последовательность стыкуемых слотов выстраивается сквозь все даты, что позволяет при выборе, например, "23:30 - 00:30" в предыдущей дате - в следующей оставить только соответствующий 00:30 (а с 00:00 и 00:15 - скрыть).
Оформление правил
В зависимости от поставленной задачи, правил можно прописать несколько или только одно.
Общий вид конструкции с правилами:

Код
Полностью скопируйте приведенный ниже код в раздел "Настройки -> Дополнительные CSS/JS -> JS", заменив содержимое конструкции var INTERVAL_OFFSET_RULES = [ ]; на Ваши правила:
<script>
var INTERVAL_OFFSET_RULES = [
{ overlapScore: 4, serviceIDs: ["11111", "22222"], aroundTheClock: true}, // круглосуточная почасовая аренда с интервалом 15 минут
{ overlapScore: 3, serviceIDs: ["33333"], aroundTheClock: false} // VR-сессии получасовыми слотами с 10-минутным перерывом
];
const filteredSequences = new Map();
let activeRule = null;
const getLabel = (el) => el.closest("label");
const getDate = (el) => el.closest(".date-row__time");
const isFree = (el) => !el.classList.contains("sequence-key");
const getBacks = () => new Set([...filteredSequences.values()].map((bundle) => bundle.back).filter(Boolean));
const getHeld = () => {
const backs = getBacks();
return new Set([...filteredSequences.values()].flatMap((bundle) => bundle.subsequentCheckboxes).filter((el) => !backs.has(el)));
};
function refreshSlots() {
const backs = getBacks();
const held = getHeld();
const hidden = new Set([...filteredSequences.values()].flatMap((bundle) => bundle.hidden));
document.querySelectorAll(".time-checkbox").forEach((el) => {
const isHidden = hidden.has(el) && !backs.has(el) && isFree(el);
getLabel(el).style.display = isHidden ? "none" : "";
if (held.has(el)) el.removeEventListener("change", sequenceToggle);
else el.addEventListener("change", sequenceToggle);
});
}
function resetOverlap() {
document.querySelectorAll(".time-checkbox").forEach((el) => {
el.removeEventListener("change", sequenceToggle);
el.classList.remove("sequence-key");
getLabel(el).style.display = "";
});
filteredSequences.clear();
}
function sequenceToggle({ target: el }) {
if (!el.checked) return sequenceDiscard(el);
sequenceAssign(el);
}
function sequenceAssign(startEl) {
const { overlapScore, aroundTheClock } = activeRule;
const all = [...document.querySelectorAll(".time-checkbox")];
const startElIndex = all.indexOf(startEl);
const backEl = all[startElIndex - overlapScore] || null;
const forward = all.slice(startElIndex + 1).filter((el) => aroundTheClock || getDate(el) === getDate(startEl));
const dates = new Set([getDate(startEl), backEl && getDate(backEl)]);
const backward = all.slice(0, startElIndex).filter((el) => el !== backEl && (aroundTheClock || dates.has(getDate(el))));
const subsequentCheckboxes = [...forward, ...backward].filter(isFree);
const hidden = [...forward.filter((el, i) => (i + 1) % overlapScore), ...backward].filter(isFree);
startEl.classList.add("sequence-key");
filteredSequences.set(startEl, { subsequentCheckboxes, hidden, backEl });
refreshSlots();
}
function sequenceDiscard(startEl) {
if (!filteredSequences.delete(startEl)) return;
startEl.classList.remove("sequence-key");
refreshSlots();
}
function slotsOverlapPrevent() {
resetOverlap();
activeRule = null;
const selectedService = document.querySelector("#service-data a[data-href].selected");
const selectedServiceId = selectedService ? selectedService.getAttribute("data-id") : undefined;
if (!selectedServiceId) return;
// поиск правила по selectedServiceId или с установленным overlapScore
const matchedRule = INTERVAL_OFFSET_RULES.find((rule) => rule.overlapScore && (!rule.serviceIDs || rule.serviceIDs.includes(selectedServiceId)));
if (!matchedRule || matchedRule.overlapScore <= 1) return;
activeRule = matchedRule;
const allCheckboxes = [...document.querySelectorAll(".time-checkbox")];
allCheckboxes.forEach((el) => el.addEventListener("change", sequenceToggle));
allCheckboxes.reverse().forEach((el) => {
if (el.checked && !getHeld().has(el)) sequenceAssign(el);
});
}
slotsOverlapPrevent();
$(document).ajaxComplete(function () {
setTimeout(slotsOverlapPrevent, 300);
});
</script>
- комментарии напротив правил (желтым текстом) даны для ориентировки, их писать не обязательно
Обратите внимание!
Чтобы выбор времени оставался возможным в услугах, к которым применяется данная модификация, свойство Мультивремени "Последовательный выбор времени" должно быть выключено!
Длительные записи клиентов будут создаваться в журнале не в виде цельной записи "с ... - до ...", а в виде серии связанных записей, каждая из которых равна одному слоту времени.
Читайте также

