Add event type handling for duties in the system
- Introduced a new `event_type` column in the `duties` table to categorize duties as 'duty', 'unavailable', or 'vacation'. - Updated the duty schedule import functionality to parse and store event types from the JSON input. - Enhanced the API response to include event types for each duty, improving the calendar display logic. - Modified the web application to visually differentiate between duty types in the calendar and duty list. - Updated tests to cover new event type functionality and ensure correct parsing and storage of duties. - Revised README documentation to reflect changes in duty event types and their representation in the system.
This commit is contained in:
@@ -198,18 +198,34 @@
|
||||
const key = localDateString(d);
|
||||
const isOther = d.getMonth() !== month;
|
||||
const dayDuties = dutiesByDate[key] || [];
|
||||
const dutyList = dayDuties.filter(function (x) { return x.event_type === "duty"; });
|
||||
const unavailableList = dayDuties.filter(function (x) { return x.event_type === "unavailable"; });
|
||||
const vacationList = dayDuties.filter(function (x) { return x.event_type === "vacation"; });
|
||||
const hasAny = dayDuties.length > 0;
|
||||
const isToday = key === today;
|
||||
const eventSummaries = calendarEventsByDate[key] || [];
|
||||
const hasEvent = eventSummaries.length > 0;
|
||||
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "day" + (isOther ? " other-month" : "") + (isToday ? " today" : "") + (dayDuties.length ? " has-duty" : "") + (hasEvent ? " holiday" : "");
|
||||
const dutyNamesAttr = dayDuties.length ? escapeHtml(dayDuties.map(function (x) { return x.full_name; }).join("\n")) : "";
|
||||
const dutyTitleAttr = dayDuties.length ? escapeHtml(dayDuties.map(function (x) { return x.full_name; }).join(", ")) : "";
|
||||
cell.innerHTML =
|
||||
"<span class=\"num\">" + d.getDate() + "</span>" +
|
||||
(dayDuties.length ? "<span class=\"duty-marker\" data-duty-names=\"" + dutyNamesAttr + "\" title=\"" + dutyTitleAttr + "\" aria-label=\"Дежурные\">Д</span>" : "") +
|
||||
(hasEvent ? "<button type=\"button\" class=\"info-btn\" aria-label=\"Информация о дне\" data-summary=\"" + escapeHtml(eventSummaries.join("\n")) + "\">i</button>" : "");
|
||||
cell.className = "day" + (isOther ? " other-month" : "") + (isToday ? " today" : "") + (hasAny ? " has-duty" : "") + (hasEvent ? " holiday" : "");
|
||||
|
||||
function namesAttr(list) { return list.length ? escapeHtml(list.map(function (x) { return x.full_name; }).join("\n")) : ""; }
|
||||
function titleAttr(list) { return list.length ? escapeHtml(list.map(function (x) { return x.full_name; }).join(", ")) : ""; }
|
||||
|
||||
let markers = "<span class=\"num\">" + d.getDate() + "</span>";
|
||||
if (dutyList.length) {
|
||||
markers += "<span class=\"duty-marker\" data-names=\"" + namesAttr(dutyList) + "\" title=\"" + titleAttr(dutyList) + "\" aria-label=\"Дежурные\">Д</span>";
|
||||
}
|
||||
if (unavailableList.length) {
|
||||
markers += "<span class=\"unavailable-marker\" data-names=\"" + namesAttr(unavailableList) + "\" title=\"" + titleAttr(unavailableList) + "\" aria-label=\"Недоступен\">Н</span>";
|
||||
}
|
||||
if (vacationList.length) {
|
||||
markers += "<span class=\"vacation-marker\" data-names=\"" + namesAttr(vacationList) + "\" title=\"" + titleAttr(vacationList) + "\" aria-label=\"Отпуск\">О</span>";
|
||||
}
|
||||
if (hasEvent) {
|
||||
markers += "<button type=\"button\" class=\"info-btn\" aria-label=\"Информация о дне\" data-summary=\"" + escapeHtml(eventSummaries.join("\n")) + "\">i</button>";
|
||||
}
|
||||
cell.innerHTML = markers;
|
||||
calendarEl.appendChild(cell);
|
||||
d.setDate(d.getDate() + 1);
|
||||
}
|
||||
@@ -312,13 +328,14 @@
|
||||
document.body.appendChild(hintEl);
|
||||
}
|
||||
var hideTimeout = null;
|
||||
calendarEl.querySelectorAll(".duty-marker").forEach(function (marker) {
|
||||
var selector = ".duty-marker, .unavailable-marker, .vacation-marker";
|
||||
calendarEl.querySelectorAll(selector).forEach(function (marker) {
|
||||
marker.addEventListener("mouseenter", function () {
|
||||
if (hideTimeout) {
|
||||
clearTimeout(hideTimeout);
|
||||
hideTimeout = null;
|
||||
}
|
||||
var names = marker.getAttribute("data-duty-names") || "";
|
||||
var names = marker.getAttribute("data-names") || "";
|
||||
hintEl.textContent = names;
|
||||
var rect = marker.getBoundingClientRect();
|
||||
positionHint(hintEl, rect);
|
||||
@@ -333,9 +350,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
var EVENT_TYPE_LABELS = { duty: "Дежурство", unavailable: "Недоступен", vacation: "Отпуск" };
|
||||
|
||||
function renderDutyList(duties) {
|
||||
if (duties.length === 0) {
|
||||
dutyListEl.innerHTML = "<p class=\"muted\">В этом месяце дежурств нет.</p>";
|
||||
dutyListEl.innerHTML = "<p class=\"muted\">В этом месяце событий нет.</p>";
|
||||
return;
|
||||
}
|
||||
const grouped = {};
|
||||
@@ -354,7 +373,9 @@
|
||||
const endDate = new Date(d.end_at);
|
||||
const start = String(startDate.getHours()).padStart(2, "0") + ":" + String(startDate.getMinutes()).padStart(2, "0");
|
||||
const end = String(endDate.getHours()).padStart(2, "0") + ":" + String(endDate.getMinutes()).padStart(2, "0");
|
||||
html += "<div class=\"duty-item\"><span class=\"name\">" + escapeHtml(d.full_name) + "</span><div class=\"time\">" + start + " – " + end + "</div></div>";
|
||||
const typeLabel = EVENT_TYPE_LABELS[d.event_type] || d.event_type;
|
||||
const itemClass = "duty-item duty-item--" + (d.event_type || "duty");
|
||||
html += "<div class=\"" + itemClass + "\"><span class=\"duty-item-type\">" + escapeHtml(typeLabel) + "</span> <span class=\"name\">" + escapeHtml(d.full_name) + "</span><div class=\"time\">" + start + " – " + end + "</div></div>";
|
||||
});
|
||||
});
|
||||
dutyListEl.innerHTML = html;
|
||||
|
||||
Reference in New Issue
Block a user