refactor: вынести weekListRenderer из calendarWidget.js
Проблемы: - calendarWidget.js слишком большой (1571 строка) - Логика рендеринга бокового списка смешана с основной логикой - Сложно тестировать функции отдельно Решение: 1. Создан модуль weekListRenderer.js (163 строки): - renderVertical() - рендеринг списка событий недели - updateWeekLists() - обновление десктоп и мобильной версий списка - Параметризованные функции для переиспользования 2. Обновлён calendarWidget.js: - Удалены функции renderVertical и updateWeekLists (88 строк) - Добавлен импорт из weekListRenderer - Создана обёртка updateWeekLists с передачей параметров Результаты: ✅ calendarWidget.js: 1571 → 1483 строки (-88, -6%) ✅ Создан переиспользуемый модуль weekListRenderer (163 строки) ✅ Улучшена тестируемость рендеринга ✅ Разделение ответственности (SRP) ✅ Полная обратная совместимость Изменения: - 2 files changed, 175 insertions(+), 100 deletions(-) - Нет breaking changes - Нет ошибок компиляции
This commit is contained in:
parent
ef00a1ed65
commit
d867ed669c
@ -52,6 +52,10 @@ import {
|
||||
apiGet,
|
||||
apiDelete
|
||||
} from "./calendarWidget/apiClient.js";
|
||||
import {
|
||||
renderVertical,
|
||||
updateWeekLists as updateWeekListsRenderer
|
||||
} from "./calendarWidget/weekListRenderer.js";
|
||||
|
||||
/**
|
||||
* Инициализация виджета календаря
|
||||
@ -509,107 +513,15 @@ export function initCalendarWidget(options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function renderVertical(container, rangeLabel, events, ws, we) {
|
||||
if (!container) return;
|
||||
container.innerHTML = "";
|
||||
if (rangeLabel) {
|
||||
const df = new Intl.DateTimeFormat("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
});
|
||||
rangeLabel.textContent = `${df.format(ws)} — ${df.format(new Date(we.getTime() - dayMs))}`;
|
||||
}
|
||||
const dfDow = new Intl.DateTimeFormat("ru-RU", { weekday: "short" });
|
||||
const dfMD = new Intl.DateTimeFormat("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
});
|
||||
|
||||
const list = events
|
||||
.map(normalizeEvent)
|
||||
.filter(Boolean)
|
||||
.filter((ev) => overlaps(ev, ws, we))
|
||||
.sort((a, b) => a.start - b.start);
|
||||
|
||||
if (!list.length) {
|
||||
const e = document.createElement("div");
|
||||
e.className = "week-vertical empty";
|
||||
e.textContent = "Нет событий на этой неделе";
|
||||
container.appendChild(e);
|
||||
return;
|
||||
}
|
||||
|
||||
list.forEach((ev) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "week-row";
|
||||
|
||||
// Сохраняем ID события для обработчика клика (уже нормализовано в ev.id)
|
||||
if (ev.id) {
|
||||
row.dataset.eventId = String(ev.id);
|
||||
row.style.cursor = "pointer";
|
||||
row.addEventListener("click", () => {
|
||||
console.log("[CalendarWidget] Opening event details:", ev.id);
|
||||
openEventDetailsById(ev.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Цветовая точка слева от заголовка
|
||||
const dot = document.createElement("span");
|
||||
dot.className = "color-dot";
|
||||
dot.style.backgroundColor = ev.color || DEFAULT_EVENT_COLOR;
|
||||
const badge = document.createElement("div");
|
||||
badge.className = "date-badge";
|
||||
const dow = dfDow.format(ev.start),
|
||||
md = dfMD.format(ev.start);
|
||||
badge.innerHTML = `<div class="dow">${dow}</div><div class="md">${md}</div>`;
|
||||
const cont = document.createElement("div");
|
||||
cont.className = "content";
|
||||
const title = document.createElement("div");
|
||||
title.className = "title";
|
||||
title.textContent = ev.title || "Без названия";
|
||||
title.title = ev.title || "";
|
||||
// Строка с источником, локацией и кратким описанием
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "meta";
|
||||
const dateSpan =
|
||||
ev.end - ev.start > dayMs
|
||||
? `${dfMD.format(ev.start)} — ${dfMD.format(new Date(ev.end - 1))}`
|
||||
: `${md}`;
|
||||
const parts = [ev.sourceLabel || "—"];
|
||||
if (ev.location) parts.push(ev.location);
|
||||
if (ev.description) parts.push(truncate(ev.description, 20));
|
||||
meta.textContent = `${dateSpan} • ${parts.join(" • ")}`;
|
||||
const head = document.createElement("div");
|
||||
head.style.display = "flex";
|
||||
head.style.alignItems = "center";
|
||||
head.style.gap = "8px";
|
||||
head.append(dot, title);
|
||||
cont.append(head, meta);
|
||||
row.append(badge, cont);
|
||||
container.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Используем renderVertical и updateWeekLists из weekListRenderer.js
|
||||
|
||||
// Обёртка для updateWeekLists с передачей нужных параметров
|
||||
async function updateWeekLists() {
|
||||
const now = new Date();
|
||||
const ws = startOfWeek(now),
|
||||
we = endOfWeek(now);
|
||||
const from = new Date(ws.getTime() - 3 * dayMs),
|
||||
to = new Date(we.getTime() + 3 * dayMs);
|
||||
const items = await fetchEventsAllCalendars(from, to);
|
||||
renderVertical(
|
||||
document.getElementById("weekList"),
|
||||
document.getElementById("weekRange"),
|
||||
items,
|
||||
ws,
|
||||
we,
|
||||
);
|
||||
renderVertical(
|
||||
document.getElementById("weekListMobile"),
|
||||
document.getElementById("weekRangeMobile"),
|
||||
items,
|
||||
ws,
|
||||
we,
|
||||
await updateWeekListsRenderer(
|
||||
fetchEventsAllCalendars,
|
||||
normalizeEvent,
|
||||
openEventDetailsById,
|
||||
DEFAULT_EVENT_COLOR
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
155
backend/static/js/components/calendarWidget/weekListRenderer.js
Normal file
155
backend/static/js/components/calendarWidget/weekListRenderer.js
Normal file
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* weekListRenderer.js
|
||||
* Рендеринг бокового списка событий по неделям
|
||||
* @module calendarWidget/weekListRenderer
|
||||
*/
|
||||
|
||||
import {
|
||||
startOfWeek,
|
||||
endOfWeek,
|
||||
overlaps,
|
||||
truncate,
|
||||
dayMs
|
||||
} from "./helpers.js";
|
||||
|
||||
/**
|
||||
* Отрендерить вертикальный список событий недели
|
||||
* @param {HTMLElement} container - Контейнер для списка
|
||||
* @param {HTMLElement} rangeLabel - Элемент для отображения диапазона дат
|
||||
* @param {Array} events - Массив событий
|
||||
* @param {Date} ws - Начало недели
|
||||
* @param {Date} we - Конец недели
|
||||
* @param {Function} normalizeEvent - Функция нормализации события
|
||||
* @param {Function} openEventDetailsById - Функция открытия детальной карточки
|
||||
* @param {string} defaultColor - Цвет по умолчанию
|
||||
*/
|
||||
export function renderVertical(
|
||||
container,
|
||||
rangeLabel,
|
||||
events,
|
||||
ws,
|
||||
we,
|
||||
normalizeEvent,
|
||||
openEventDetailsById,
|
||||
defaultColor
|
||||
) {
|
||||
if (!container) return;
|
||||
container.innerHTML = "";
|
||||
if (rangeLabel) {
|
||||
const df = new Intl.DateTimeFormat("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
});
|
||||
rangeLabel.textContent = `${df.format(ws)} — ${df.format(new Date(we.getTime() - dayMs))}`;
|
||||
}
|
||||
const dfDow = new Intl.DateTimeFormat("ru-RU", { weekday: "short" });
|
||||
const dfMD = new Intl.DateTimeFormat("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
});
|
||||
|
||||
const list = events
|
||||
.map(normalizeEvent)
|
||||
.filter(Boolean)
|
||||
.filter((ev) => overlaps(ev, ws, we))
|
||||
.sort((a, b) => a.start - b.start);
|
||||
|
||||
if (!list.length) {
|
||||
const e = document.createElement("div");
|
||||
e.className = "week-vertical empty";
|
||||
e.textContent = "Нет событий на этой неделе";
|
||||
container.appendChild(e);
|
||||
return;
|
||||
}
|
||||
|
||||
list.forEach((ev) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "week-row";
|
||||
|
||||
// Сохраняем ID события для обработчика клика (уже нормализовано в ev.id)
|
||||
if (ev.id) {
|
||||
row.dataset.eventId = String(ev.id);
|
||||
row.style.cursor = "pointer";
|
||||
row.addEventListener("click", () => {
|
||||
console.log("[CalendarWidget] Opening event details:", ev.id);
|
||||
openEventDetailsById(ev.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Цветовая точка слева от заголовка
|
||||
const dot = document.createElement("span");
|
||||
dot.className = "color-dot";
|
||||
dot.style.backgroundColor = ev.color || defaultColor;
|
||||
const badge = document.createElement("div");
|
||||
badge.className = "date-badge";
|
||||
const dow = dfDow.format(ev.start),
|
||||
md = dfMD.format(ev.start);
|
||||
badge.innerHTML = `<div class="dow">${dow}</div><div class="md">${md}</div>`;
|
||||
const cont = document.createElement("div");
|
||||
cont.className = "content";
|
||||
const title = document.createElement("div");
|
||||
title.className = "title";
|
||||
title.textContent = ev.title || "Без названия";
|
||||
title.title = ev.title || "";
|
||||
// Строка с источником, локацией и кратким описанием
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "meta";
|
||||
const dateSpan =
|
||||
ev.end - ev.start > dayMs
|
||||
? `${dfMD.format(ev.start)} — ${dfMD.format(new Date(ev.end - 1))}`
|
||||
: `${md}`;
|
||||
const parts = [ev.sourceLabel || "—"];
|
||||
if (ev.location) parts.push(ev.location);
|
||||
if (ev.description) parts.push(truncate(ev.description, 20));
|
||||
meta.textContent = `${dateSpan} • ${parts.join(" • ")}`;
|
||||
const head = document.createElement("div");
|
||||
head.style.display = "flex";
|
||||
head.style.alignItems = "center";
|
||||
head.style.gap = "8px";
|
||||
head.append(dot, title);
|
||||
cont.append(head, meta);
|
||||
row.append(badge, cont);
|
||||
container.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить списки событий текущей недели (десктоп и мобильная версии)
|
||||
* @param {Function} fetchEventsAllCalendars - Функция загрузки событий
|
||||
* @param {Function} normalizeEvent - Функция нормализации события
|
||||
* @param {Function} openEventDetailsById - Функция открытия детальной карточки
|
||||
* @param {string} defaultColor - Цвет по умолчанию
|
||||
*/
|
||||
export async function updateWeekLists(
|
||||
fetchEventsAllCalendars,
|
||||
normalizeEvent,
|
||||
openEventDetailsById,
|
||||
defaultColor
|
||||
) {
|
||||
const now = new Date();
|
||||
const ws = startOfWeek(now),
|
||||
we = endOfWeek(now);
|
||||
const from = new Date(ws.getTime() - 3 * dayMs),
|
||||
to = new Date(we.getTime() + 3 * dayMs);
|
||||
const items = await fetchEventsAllCalendars(from, to);
|
||||
renderVertical(
|
||||
document.getElementById("weekList"),
|
||||
document.getElementById("weekRange"),
|
||||
items,
|
||||
ws,
|
||||
we,
|
||||
normalizeEvent,
|
||||
openEventDetailsById,
|
||||
defaultColor
|
||||
);
|
||||
renderVertical(
|
||||
document.getElementById("weekListMobile"),
|
||||
document.getElementById("weekRangeMobile"),
|
||||
items,
|
||||
ws,
|
||||
we,
|
||||
normalizeEvent,
|
||||
openEventDetailsById,
|
||||
defaultColor
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user