Refactor calendar rendering and duty list display

- Updated the calendar cell rendering to conditionally show duty and event markers based on the month view.
- Introduced a new HTML structure for day markers to improve layout and styling.
- Enhanced the duty list rendering to filter duties based on the current month, ensuring only relevant duties are displayed.
- Added CSS styles for day markers to improve visual presentation and alignment.
This commit is contained in:
2026-02-17 23:26:41 +03:00
parent 3f4c7bf66c
commit e23d2247e0
2 changed files with 60 additions and 22 deletions

View File

@@ -207,25 +207,29 @@
const hasEvent = eventSummaries.length > 0;
const cell = document.createElement("div");
cell.className = "day" + (isOther ? " other-month" : "") + (isToday ? " today" : "") + (hasAny ? " has-duty" : "") + (hasEvent ? " holiday" : "");
const showMarkers = !isOther;
cell.className = "day" + (isOther ? " other-month" : "") + (isToday ? " today" : "") + (showMarkers && hasAny ? " has-duty" : "") + (showMarkers && 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>";
let html = "<span class=\"num\">" + d.getDate() + "</span><div class=\"day-markers\">";
if (showMarkers) {
if (dutyList.length) {
html += "<span class=\"duty-marker\" data-names=\"" + namesAttr(dutyList) + "\" title=\"" + titleAttr(dutyList) + "\" aria-label=\"Дежурные\">Д</span>";
}
if (unavailableList.length) {
html += "<span class=\"unavailable-marker\" data-names=\"" + namesAttr(unavailableList) + "\" title=\"" + titleAttr(unavailableList) + "\" aria-label=\"Недоступен\">Н</span>";
}
if (vacationList.length) {
html += "<span class=\"vacation-marker\" data-names=\"" + namesAttr(vacationList) + "\" title=\"" + titleAttr(vacationList) + "\" aria-label=\"Отпуск\">О</span>";
}
if (hasEvent) {
html += "<button type=\"button\" class=\"info-btn\" aria-label=\"Информация о дне\" data-summary=\"" + escapeHtml(eventSummaries.join("\n")) + "\">i</button>";
}
}
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;
html += "</div>";
cell.innerHTML = html;
calendarEl.appendChild(cell);
d.setDate(d.getDate() + 1);
}
@@ -470,8 +474,12 @@
setNavEnabled(false);
loadingEl.classList.remove("hidden");
errorEl.hidden = true;
const from = localDateString(firstDayOfMonth(current));
const to = localDateString(lastDayOfMonth(current));
const first = firstDayOfMonth(current);
const start = getMonday(first);
const gridEnd = new Date(start);
gridEnd.setDate(gridEnd.getDate() + 41);
const from = localDateString(start);
const to = localDateString(gridEnd);
try {
const dutiesPromise = fetchDuties(from, to);
const eventsPromise = fetchCalendarEvents(from, to);
@@ -480,7 +488,16 @@
const byDate = dutiesByDate(duties);
const calendarByDate = calendarEventsByDate(events);
renderCalendar(current.getFullYear(), current.getMonth(), byDate, calendarByDate);
renderDutyList(duties);
const last = lastDayOfMonth(current);
const firstKey = localDateString(first);
const lastKey = localDateString(last);
const dutiesInMonth = duties.filter(function (d) {
const byDateLocal = dutiesByDate([d]);
return Object.keys(byDateLocal).some(function (key) {
return key >= firstKey && key <= lastKey;
});
});
renderDutyList(dutiesInMonth);
} catch (e) {
if (e.message === "ACCESS_DENIED") {
showAccessDenied(e.serverDetail);

View File

@@ -143,6 +143,17 @@ body {
opacity: 0.9;
}
.day-markers {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 2px;
align-items: center;
margin-top: 2px;
min-width: 0;
}
.calendar-event-hint {
position: fixed;
z-index: 1000;
@@ -170,10 +181,9 @@ body {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 0.65rem;
width: 14px;
height: 14px;
font-size: 0.6rem;
font-weight: 700;
border-radius: 50%;
flex-shrink: 0;
@@ -205,6 +215,10 @@ body {
}
.duty-item {
display: grid;
grid-template-columns: 5.5em 1fr;
gap: 0 8px;
align-items: baseline;
padding: 8px 10px;
margin-bottom: 6px;
border-radius: 8px;
@@ -221,16 +235,23 @@ body {
}
.duty-item .duty-item-type {
grid-column: 1;
grid-row: 1;
font-size: 0.75rem;
color: var(--muted);
margin-right: 4px;
}
.duty-item .name {
grid-column: 2;
grid-row: 1 / -1;
min-width: 0;
font-weight: 600;
}
.duty-item .time {
grid-column: 1;
grid-row: 2;
align-self: start;
font-size: 0.8rem;
color: var(--muted);
}