refactor: restructure web application with modular JavaScript and remove legacy code
- Deleted the `app.js` file and migrated its functionality to a modular structure with multiple JavaScript files for better organization and maintainability. - Updated `index.html` to reference the new `main.js` module, ensuring proper loading of the application. - Introduced new utility modules for API requests, authentication, calendar handling, and DOM manipulation to enhance code clarity and separation of concerns. - Enhanced CSS styles for improved layout and theming consistency across the application. - Added comprehensive comments and documentation to new modules to facilitate future development and understanding.
This commit is contained in:
157
webapp/js/calendar.js
Normal file
157
webapp/js/calendar.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Calendar grid and events-by-date mapping.
|
||||
*/
|
||||
|
||||
import { calendarEl, monthTitleEl } from "./dom.js";
|
||||
import { MONTHS } from "./constants.js";
|
||||
import { escapeHtml } from "./utils.js";
|
||||
import {
|
||||
localDateString,
|
||||
firstDayOfMonth,
|
||||
lastDayOfMonth,
|
||||
getMonday
|
||||
} from "./dateUtils.js";
|
||||
import { bindInfoButtonTooltips } from "./hints.js";
|
||||
import { bindDutyMarkerTooltips } from "./hints.js";
|
||||
|
||||
/**
|
||||
* Build { localDateKey -> array of summary strings }. API date is UTC YYYY-MM-DD; map to local.
|
||||
* @param {object[]} events - Calendar events with date, summary
|
||||
* @returns {Record<string, string[]>}
|
||||
*/
|
||||
export function calendarEventsByDate(events) {
|
||||
const byDate = {};
|
||||
(events || []).forEach((e) => {
|
||||
const utcMidnight = new Date(e.date + "T00:00:00Z");
|
||||
const key = localDateString(utcMidnight);
|
||||
if (!byDate[key]) byDate[key] = [];
|
||||
if (e.summary) byDate[key].push(e.summary);
|
||||
});
|
||||
return byDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group duties by local date (start_at/end_at are UTC).
|
||||
* @param {object[]} duties - Duties with start_at, end_at
|
||||
* @returns {Record<string, object[]>}
|
||||
*/
|
||||
export function dutiesByDate(duties) {
|
||||
const byDate = {};
|
||||
duties.forEach((d) => {
|
||||
const start = new Date(d.start_at);
|
||||
const end = new Date(d.end_at);
|
||||
const endLocal = localDateString(end);
|
||||
let t = new Date(start);
|
||||
while (true) {
|
||||
const key = localDateString(t);
|
||||
if (!byDate[key]) byDate[key] = [];
|
||||
byDate[key].push(d);
|
||||
if (key === endLocal) break;
|
||||
t.setDate(t.getDate() + 1);
|
||||
}
|
||||
});
|
||||
return byDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render calendar grid for given month with duties and calendar events.
|
||||
* @param {number} year
|
||||
* @param {number} month - 0-based
|
||||
* @param {Record<string, object[]>} dutiesByDateMap
|
||||
* @param {Record<string, string[]>} calendarEventsByDateMap
|
||||
*/
|
||||
export function renderCalendar(
|
||||
year,
|
||||
month,
|
||||
dutiesByDateMap,
|
||||
calendarEventsByDateMap
|
||||
) {
|
||||
if (!calendarEl || !monthTitleEl) return;
|
||||
const first = firstDayOfMonth(new Date(year, month, 1));
|
||||
const last = lastDayOfMonth(new Date(year, month, 1));
|
||||
const start = getMonday(first);
|
||||
const today = localDateString(new Date());
|
||||
const calendarEventsByDate = calendarEventsByDateMap || {};
|
||||
|
||||
calendarEl.innerHTML = "";
|
||||
let d = new Date(start);
|
||||
const cells = 42;
|
||||
for (let i = 0; i < cells; i++) {
|
||||
const key = localDateString(d);
|
||||
const isOther = d.getMonth() !== month;
|
||||
const dayDuties = dutiesByDateMap[key] || [];
|
||||
const dutyList = dayDuties.filter((x) => x.event_type === "duty");
|
||||
const unavailableList = dayDuties.filter((x) => x.event_type === "unavailable");
|
||||
const vacationList = dayDuties.filter((x) => x.event_type === "vacation");
|
||||
const hasAny = dayDuties.length > 0;
|
||||
const isToday = key === today;
|
||||
const eventSummaries = calendarEventsByDate[key] || [];
|
||||
const hasEvent = eventSummaries.length > 0;
|
||||
|
||||
const showMarkers = !isOther;
|
||||
const cell = document.createElement("div");
|
||||
cell.className =
|
||||
"day" +
|
||||
(isOther ? " other-month" : "") +
|
||||
(isToday ? " today" : "") +
|
||||
(showMarkers && hasAny ? " has-duty" : "") +
|
||||
(showMarkers && hasEvent ? " holiday" : "");
|
||||
|
||||
const namesAttr = (list) =>
|
||||
list.length
|
||||
? escapeHtml(list.map((x) => x.full_name).join("\n"))
|
||||
: "";
|
||||
const dutyItemsJson = dutyList.length
|
||||
? JSON.stringify(
|
||||
dutyList.map((x) => ({
|
||||
full_name: x.full_name,
|
||||
start_at: x.start_at,
|
||||
end_at: x.end_at
|
||||
}))
|
||||
).replace(/'/g, "'")
|
||||
: "";
|
||||
|
||||
let html =
|
||||
'<span class="num">' +
|
||||
d.getDate() +
|
||||
'</span><div class="day-markers">';
|
||||
if (showMarkers) {
|
||||
if (dutyList.length) {
|
||||
html +=
|
||||
'<button type="button" class="duty-marker" data-event-type="duty" data-date="' +
|
||||
escapeHtml(key) +
|
||||
'" data-names="' +
|
||||
namesAttr(dutyList) +
|
||||
'" data-duty-items=\'' +
|
||||
dutyItemsJson +
|
||||
"' aria-label=\"Дежурные\">Д</button>";
|
||||
}
|
||||
if (unavailableList.length) {
|
||||
html +=
|
||||
'<button type="button" class="unavailable-marker" data-event-type="unavailable" data-names="' +
|
||||
namesAttr(unavailableList) +
|
||||
'" aria-label="Недоступен">Н</button>';
|
||||
}
|
||||
if (vacationList.length) {
|
||||
html +=
|
||||
'<button type="button" class="vacation-marker" data-event-type="vacation" data-names="' +
|
||||
namesAttr(vacationList) +
|
||||
'" aria-label="Отпуск">О</button>';
|
||||
}
|
||||
if (hasEvent) {
|
||||
html +=
|
||||
'<button type="button" class="info-btn" aria-label="Информация о дне" data-summary="' +
|
||||
escapeHtml(eventSummaries.join("\n")) +
|
||||
'">i</button>';
|
||||
}
|
||||
}
|
||||
html += "</div>";
|
||||
cell.innerHTML = html;
|
||||
calendarEl.appendChild(cell);
|
||||
d.setDate(d.getDate() + 1);
|
||||
}
|
||||
|
||||
monthTitleEl.textContent = MONTHS[month] + " " + year;
|
||||
bindInfoButtonTooltips();
|
||||
bindDutyMarkerTooltips();
|
||||
}
|
||||
Reference in New Issue
Block a user