feat: implement day detail panel for calendar
- Introduced a new `dayDetail.js` module to manage the day detail panel functionality, allowing users to view detailed information about duties and events for each calendar day. - Enhanced the calendar rendering in `calendar.js` to include visual indicators for duties and events, improving user interaction and experience. - Updated CSS in `style.css` to style the day detail panel and its components, ensuring a responsive design for both desktop and mobile views. - Refactored `hints.js` to export the `getDutyMarkerRows` function, facilitating better integration with the new day detail features. - Added localization support for the day detail panel in `i18n.js`, including new translations for close button and event titles. - Enhanced the initialization process in `main.js` to set up the day detail panel on application load.
This commit is contained in:
@@ -9,10 +9,9 @@ import {
|
||||
localDateString,
|
||||
firstDayOfMonth,
|
||||
lastDayOfMonth,
|
||||
getMonday
|
||||
getMonday,
|
||||
dateKeyToDDMM
|
||||
} 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.
|
||||
@@ -88,71 +87,65 @@ export function renderCalendar(
|
||||
const eventSummaries = calendarEventsByDate[key] || [];
|
||||
const hasEvent = eventSummaries.length > 0;
|
||||
|
||||
const showMarkers = !isOther;
|
||||
const showIndicator = !isOther;
|
||||
const cell = document.createElement("div");
|
||||
cell.className =
|
||||
"day" +
|
||||
(isOther ? " other-month" : "") +
|
||||
(isToday ? " today" : "") +
|
||||
(showMarkers && hasAny ? " has-duty" : "") +
|
||||
(showMarkers && hasEvent ? " holiday" : "");
|
||||
(showIndicator && hasAny ? " has-duty" : "") +
|
||||
(showIndicator && 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) {
|
||||
const lang = state.lang;
|
||||
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=\"" + escapeHtml(t(lang, "aria.duty")) + "\">Д</button>";
|
||||
}
|
||||
if (unavailableList.length) {
|
||||
html +=
|
||||
'<button type="button" class="unavailable-marker" data-event-type="unavailable" data-names="' +
|
||||
namesAttr(unavailableList) +
|
||||
'" aria-label="' + escapeHtml(t(lang, "aria.unavailable")) + '">Н</button>';
|
||||
}
|
||||
if (vacationList.length) {
|
||||
html +=
|
||||
'<button type="button" class="vacation-marker" data-event-type="vacation" data-names="' +
|
||||
namesAttr(vacationList) +
|
||||
'" aria-label="' + escapeHtml(t(lang, "aria.vacation")) + '">О</button>';
|
||||
}
|
||||
if (hasEvent) {
|
||||
html +=
|
||||
'<button type="button" class="info-btn" aria-label="' + escapeHtml(t(lang, "aria.day_info")) + '" data-summary="' +
|
||||
escapeHtml(eventSummaries.join("\n")) +
|
||||
'">i</button>';
|
||||
}
|
||||
cell.setAttribute("data-date", key);
|
||||
if (showIndicator) {
|
||||
const dayPayload = dayDuties.map((x) => ({
|
||||
event_type: x.event_type,
|
||||
full_name: x.full_name,
|
||||
start_at: x.start_at,
|
||||
end_at: x.end_at
|
||||
}));
|
||||
cell.setAttribute(
|
||||
"data-day-duties",
|
||||
JSON.stringify(dayPayload).replace(/"/g, """)
|
||||
);
|
||||
cell.setAttribute(
|
||||
"data-day-events",
|
||||
JSON.stringify(eventSummaries).replace(/"/g, """)
|
||||
);
|
||||
}
|
||||
html += "</div>";
|
||||
cell.innerHTML = html;
|
||||
|
||||
const ariaParts = [];
|
||||
ariaParts.push(dateKeyToDDMM(key));
|
||||
if (hasAny || hasEvent) {
|
||||
const counts = [];
|
||||
const lang = state.lang;
|
||||
if (dutyList.length) counts.push(dutyList.length + " " + t(lang, "event_type.duty"));
|
||||
if (unavailableList.length) counts.push(unavailableList.length + " " + t(lang, "event_type.unavailable"));
|
||||
if (vacationList.length) counts.push(vacationList.length + " " + t(lang, "event_type.vacation"));
|
||||
if (hasEvent) counts.push(t(lang, "hint.events"));
|
||||
ariaParts.push(counts.join(", "));
|
||||
} else {
|
||||
ariaParts.push(t(state.lang, "aria.day_info"));
|
||||
}
|
||||
cell.setAttribute("role", "button");
|
||||
cell.setAttribute("tabindex", "0");
|
||||
cell.setAttribute("aria-label", ariaParts.join("; "));
|
||||
|
||||
let indicatorHtml = "";
|
||||
if (showIndicator && (hasAny || hasEvent)) {
|
||||
indicatorHtml = '<div class="day-indicator">';
|
||||
if (dutyList.length) indicatorHtml += '<span class="day-indicator-dot duty"></span>';
|
||||
if (unavailableList.length) indicatorHtml += '<span class="day-indicator-dot unavailable"></span>';
|
||||
if (vacationList.length) indicatorHtml += '<span class="day-indicator-dot vacation"></span>';
|
||||
if (hasEvent) indicatorHtml += '<span class="day-indicator-dot events"></span>';
|
||||
indicatorHtml += "</div>";
|
||||
}
|
||||
|
||||
cell.innerHTML =
|
||||
'<span class="num">' + d.getDate() + "</span>" + indicatorHtml;
|
||||
calendarEl.appendChild(cell);
|
||||
d.setDate(d.getDate() + 1);
|
||||
}
|
||||
|
||||
monthTitleEl.textContent = monthName(state.lang, month) + " " + year;
|
||||
bindInfoButtonTooltips();
|
||||
bindDutyMarkerTooltips();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user