Initial commit: site monorepo with API, web, and infra.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Диалог «На какой срок…» для skip/replace/adjustment плана на день.
|
||||
*/
|
||||
(function (global) {
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function weekEndIso(planDate) {
|
||||
const parts = String(planDate || "").split("-");
|
||||
const d = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
|
||||
const jsDay = d.getDay();
|
||||
const pyWeekday = jsDay === 0 ? 6 : jsDay - 1;
|
||||
d.setDate(d.getDate() + (6 - pyWeekday));
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
function todayIso() {
|
||||
const d = new Date();
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function dismissOverlays(host, keepSelectors) {
|
||||
host.querySelectorAll(".zt-k-hub-plan-overlay").forEach((el) => {
|
||||
if (keepSelectors.some((sel) => el.matches(sel))) return;
|
||||
el.remove();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string} options.planDate YYYY-MM-DD
|
||||
* @param {string} [options.title]
|
||||
* @param {HTMLElement} [options.overlayHost] куда монтировать overlay
|
||||
* @param {string[]} [options.keepOverlays] селекторы overlay, которые не закрывать
|
||||
* @param {function} [options.onError]
|
||||
* @param {function} [options.beforeAppend] (overlay) => void — dismiss other overlays
|
||||
*/
|
||||
function promptSkipDuration(options) {
|
||||
const planDate = options.planDate || todayIso();
|
||||
const title = options.title || "На какой срок исключить?";
|
||||
const onError = options.onError || function () {};
|
||||
const host = options.overlayHost || document.body;
|
||||
const keepOverlays = options.keepOverlays || [];
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const weekEnd = weekEndIso(planDate);
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "zt-k-hub-plan-overlay zt-k-hub-plan-skip-duration";
|
||||
overlay.innerHTML =
|
||||
'<div class="zt-k-hub-plan-subdialog zt-k-hub-plan-skip-duration__dialog" role="dialog" aria-modal="true">' +
|
||||
`<p class="zt-k-hub-plan-subdialog__title zt-k-hub-plan-skip-duration__title">${escapeHtml(title)}</p>` +
|
||||
'<div class="zt-k-hub-plan-skip-duration__options">' +
|
||||
'<label class="zt-k-hub-plan-skip-duration__option">' +
|
||||
'<input type="radio" name="skip-duration" value="today" checked> Только сегодня</label>' +
|
||||
'<label class="zt-k-hub-plan-skip-duration__option">' +
|
||||
`<input type="radio" name="skip-duration" value="week"> До конца недели (до ${escapeHtml(weekEnd)})</label>` +
|
||||
'<label class="zt-k-hub-plan-skip-duration__option zt-k-hub-plan-skip-duration__option--date">' +
|
||||
'<input type="radio" name="skip-duration" value="date"> До даты ' +
|
||||
`<input type="date" class="form-control" data-skip-until-date min="${escapeHtml(planDate)}" value="${escapeHtml(planDate)}">` +
|
||||
"</label></div>" +
|
||||
'<div class="zt-k-hub-plan-subdialog__actions zt-k-hub-plan-skip-duration__actions">' +
|
||||
'<button type="button" class="btn btn-secondary" data-skip-duration-cancel>Отмена</button>' +
|
||||
'<button type="button" class="btn btn-primary" data-skip-duration-confirm>Применить</button>' +
|
||||
"</div></div>";
|
||||
if (typeof options.beforeAppend === "function") {
|
||||
options.beforeAppend(overlay);
|
||||
} else if (keepOverlays.length) {
|
||||
dismissOverlays(host, keepOverlays);
|
||||
}
|
||||
overlay.setAttribute("data-daily-plan-overlay", "1");
|
||||
host.appendChild(overlay);
|
||||
const dateInput = overlay.querySelector("[data-skip-until-date]");
|
||||
const finish = (value) => {
|
||||
if (overlay.isConnected) overlay.remove();
|
||||
resolve(value);
|
||||
};
|
||||
overlay.addEventListener("click", (event) => {
|
||||
if (event.target === overlay) finish(null);
|
||||
});
|
||||
overlay.querySelector("[data-skip-duration-cancel]")?.addEventListener("click", () => finish(null));
|
||||
overlay.querySelector("[data-skip-duration-confirm]")?.addEventListener("click", () => {
|
||||
const selected = overlay.querySelector('input[name="skip-duration"]:checked');
|
||||
const mode = selected?.value || "today";
|
||||
if (mode === "week") {
|
||||
finish({ duration: "week" });
|
||||
return;
|
||||
}
|
||||
if (mode === "date") {
|
||||
const untilDate = dateInput?.value || planDate;
|
||||
if (untilDate < planDate) {
|
||||
onError("Дата окончания не может быть раньше начала");
|
||||
return;
|
||||
}
|
||||
finish({ duration: "date", untilDate });
|
||||
return;
|
||||
}
|
||||
finish({ duration: "today" });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function skipDurationBody(planDate, extra, title, options) {
|
||||
return promptSkipDuration({ planDate, title, ...options }).then((choice) => {
|
||||
if (!choice) return null;
|
||||
return { date: planDate, ...choice, ...(extra || {}) };
|
||||
});
|
||||
}
|
||||
|
||||
global.WespDailyPlanSkipDuration = {
|
||||
weekEndIso,
|
||||
todayIso,
|
||||
promptSkipDuration,
|
||||
skipDurationBody,
|
||||
escapeHtml,
|
||||
};
|
||||
})(window);
|
||||
Reference in New Issue
Block a user