Initial commit: site monorepo with API, web, and infra.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
влад
2026-07-16 10:11:54 +03:00
co-authored by Cursor
commit 016910ffb7
447 changed files with 73972 additions and 0 deletions
@@ -0,0 +1,315 @@
/**
* Компактный Date Range Picker для /reports.
* Сохраняет #date-from / #date-to для совместимости с остальным кодом.
*/
(function (global) {
const MONTHS = [
"янв",
"фев",
"мар",
"апр",
"май",
"июн",
"июл",
"авг",
"сен",
"окт",
"ноя",
"дек",
];
let draftFrom = null;
let draftTo = null;
let viewMonth = null;
let activePreset = "day";
let panelEl = null;
let triggerEl = null;
function pad(n) {
return String(n).padStart(2, "0");
}
function toIso(d) {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
function parseIso(s) {
if (!s) return null;
const p = String(s).split("-");
if (p.length !== 3) return null;
const d = new Date(Number(p[0]), Number(p[1]) - 1, Number(p[2]));
d.setHours(0, 0, 0, 0);
return d;
}
function startOfDay(d) {
const x = new Date(d);
x.setHours(0, 0, 0, 0);
return x;
}
function today() {
return startOfDay(new Date());
}
function sameDay(a, b) {
return a && b && toIso(a) === toIso(b);
}
function formatLabel(from, to) {
if (!from || !to) return "Выберите даты";
const t = today();
const fmt = (d) => `${pad(d.getDate())}.${pad(d.getMonth() + 1)}`;
if (sameDay(from, to) && sameDay(from, t)) return "Сегодня";
if (sameDay(from, to)) return fmt(from);
return `${fmt(from)}${fmt(to)}`;
}
function syncHiddenInputs(from, to) {
const fromEl = document.getElementById("date-from");
const toEl = document.getElementById("date-to");
if (fromEl) fromEl.value = from ? toIso(from) : "";
if (toEl) toEl.value = to ? toIso(to) : "";
}
function readCommittedRange() {
const fromEl = document.getElementById("date-from");
const toEl = document.getElementById("date-to");
return {
from: parseIso(fromEl?.value),
to: parseIso(toEl?.value),
};
}
function updateTriggerLabel() {
const label = document.getElementById("reportsDateRangeLabel");
if (!label) return;
const { from, to } = readCommittedRange();
label.textContent = formatLabel(from, to);
}
function setPresetActive(preset) {
activePreset = preset || "";
document.querySelectorAll("[data-drp-preset]").forEach((btn) => {
btn.classList.toggle("active", btn.getAttribute("data-drp-preset") === activePreset);
});
}
function computePresetRange(preset) {
const t = today();
let from = t;
let to = t;
if (preset === "week") {
from = new Date(t);
from.setDate(t.getDate() - ((t.getDay() + 6) % 7));
} else if (preset === "month") {
from = new Date(t.getFullYear(), t.getMonth(), 1);
}
return { from: startOfDay(from), to: startOfDay(to) };
}
function commitDraft() {
if (draftFrom && !draftTo) draftTo = draftFrom;
if (!draftFrom || !draftTo) return readCommittedRange();
syncHiddenInputs(draftFrom, draftTo);
setPresetActive("");
updateTriggerLabel();
return { from: draftFrom, to: draftTo };
}
function applyRange(from, to, preset) {
syncHiddenInputs(from, to);
draftFrom = from;
draftTo = to;
setPresetActive(preset || "");
updateTriggerLabel();
}
function commitSelection() {
commitDraft();
closePanel();
}
function positionPanel() {
if (!triggerEl || !panelEl || panelEl.hidden) return;
const rect = triggerEl.getBoundingClientRect();
const width = Math.max(rect.width, 304);
let left = rect.left;
if (left + width > window.innerWidth - 12) {
left = Math.max(12, window.innerWidth - width - 12);
}
let top = rect.bottom + 6;
const panelHeight = panelEl.offsetHeight || 360;
if (top + panelHeight > window.innerHeight - 12) {
top = Math.max(12, rect.top - panelHeight - 6);
}
panelEl.style.left = `${left}px`;
panelEl.style.top = `${top}px`;
panelEl.style.width = `${width}px`;
}
function closePanel() {
if (panelEl) panelEl.hidden = true;
if (triggerEl) triggerEl.setAttribute("aria-expanded", "false");
window.removeEventListener("resize", positionPanel);
window.removeEventListener("scroll", positionPanel, true);
}
function openPanel() {
const committed = readCommittedRange();
draftFrom = committed.from || today();
draftTo = committed.to || draftFrom;
viewMonth = new Date(draftFrom.getFullYear(), draftFrom.getMonth(), 1);
renderCalendar();
if (panelEl) panelEl.hidden = false;
if (triggerEl) triggerEl.setAttribute("aria-expanded", "true");
positionPanel();
window.addEventListener("resize", positionPanel);
window.addEventListener("scroll", positionPanel, true);
}
function togglePanel() {
if (panelEl?.hidden) openPanel();
else closePanel();
}
function inRange(d, a, b) {
if (!a || !b) return false;
const t = d.getTime();
const lo = Math.min(a.getTime(), b.getTime());
const hi = Math.max(a.getTime(), b.getTime());
return t >= lo && t <= hi;
}
function onDayClick(d) {
if (!draftFrom || (draftFrom && draftTo)) {
draftFrom = d;
draftTo = null;
setPresetActive("");
renderCalendar();
return;
}
if (d < draftFrom) {
draftTo = draftFrom;
draftFrom = d;
} else {
draftTo = d;
}
setPresetActive("");
commitSelection();
}
function renderCalendar() {
const grid = document.getElementById("reportsDateRangeGrid");
const title = document.getElementById("reportsDateRangeMonth");
if (!grid || !viewMonth) return;
const y = viewMonth.getFullYear();
const m = viewMonth.getMonth();
if (title) title.textContent = `${MONTHS[m]} ${y}`;
const first = new Date(y, m, 1);
const startOffset = (first.getDay() + 6) % 7;
const daysInMonth = new Date(y, m + 1, 0).getDate();
let html = "";
for (let i = 0; i < startOffset; i++) {
html += '<span class="reports-drp__day reports-drp__day--empty"></span>';
}
for (let day = 1; day <= daysInMonth; day++) {
const d = new Date(y, m, day);
const classes = ["reports-drp__day"];
if (sameDay(d, today())) classes.push("reports-drp__day--today");
if (draftFrom && sameDay(d, draftFrom)) classes.push("reports-drp__day--edge");
if (draftTo && sameDay(d, draftTo)) classes.push("reports-drp__day--edge");
if (inRange(d, draftFrom, draftTo)) classes.push("reports-drp__day--in-range");
html += `<button type="button" class="${classes.join(" ")}" data-drp-day="${toIso(d)}">${day}</button>`;
}
grid.innerHTML = html;
grid.querySelectorAll("[data-drp-day]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const d = parseIso(btn.getAttribute("data-drp-day"));
if (d) onDayClick(d);
});
});
}
function setPeriod(period) {
const { from, to } = computePresetRange(period);
applyRange(from, to, period);
closePanel();
}
function init(options) {
triggerEl = document.getElementById("reportsDateRangeTrigger");
panelEl = document.getElementById("reportsDateRangePanel");
if (!triggerEl || !panelEl) return;
if (panelEl.parentElement !== document.body) {
document.body.appendChild(panelEl);
}
panelEl.addEventListener("mousedown", (e) => e.stopPropagation());
panelEl.addEventListener("click", (e) => e.stopPropagation());
document.querySelectorAll("[data-drp-preset]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
setPeriod(btn.getAttribute("data-drp-preset") || "day");
});
});
document.getElementById("reportsDateRangePrev")?.addEventListener("click", (e) => {
e.stopPropagation();
if (!viewMonth) viewMonth = today();
viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() - 1, 1);
renderCalendar();
positionPanel();
});
document.getElementById("reportsDateRangeNext")?.addEventListener("click", (e) => {
e.stopPropagation();
if (!viewMonth) viewMonth = today();
viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 1);
renderCalendar();
positionPanel();
});
triggerEl.addEventListener("click", (e) => {
e.stopPropagation();
togglePanel();
});
document.addEventListener("click", () => {
if (!panelEl.hidden) closePanel();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closePanel();
});
const urlParams = new URLSearchParams(window.location.search);
const highlightReportId = urlParams.get("report");
if highlightReportId) {
global.__wespHighlightReportId = highlightReportId;
setPeriod("month");
} else {
setPeriod("month");
}
if (options?.onReady) options.onReady();
}
global.WespReportsDateRange = {
init,
setPeriod,
commit: commitDraft,
updateTriggerLabel,
formatLabel,
parseIso,
toIso,
};
global.setPeriod = setPeriod;
})(typeof window !== "undefined" ? window : globalThis);