From 6d91274a4eb02110984533a2923324421ca7523c Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov Date: Thu, 19 Feb 2026 15:00:17 +0300 Subject: [PATCH] fix: enhance duty marker hint rendering and styling - Introduced a new function `getDutyMarkerHintHtml` to generate HTML for duty hints, improving the display of duty times and names. - Updated the CSS for `.calendar-event-hint` to enhance layout and styling, ensuring better visual alignment and readability. - Adjusted the handling of duty items to include non-breaking spaces for improved formatting of time displays. - Removed deprecated CSS rules related to sticky elements to streamline styles and improve maintainability. --- webapp/app.js | 86 ++++++++++++++++++++++++++++++++++++++++++------ webapp/style.css | 52 +++++++++++++++++++++-------- 2 files changed, 114 insertions(+), 24 deletions(-) diff --git a/webapp/app.js b/webapp/app.js index a9a6c24..2705393 100644 --- a/webapp/app.js +++ b/webapp/app.js @@ -431,6 +431,7 @@ }); if (dutyItems.length >= 1 && hasTimes) { var hintDay = marker.getAttribute("data-date") || ""; + var nbsp = "\u00a0"; body = dutyItems.map(function (item, idx) { var startAt = item.start_at != null ? item.start_at : item.startAt; var endAt = item.end_at != null ? item.end_at : item.endAt; @@ -439,19 +440,19 @@ var startSameDay = hintDay && startAt && localDateString(new Date(startAt)) === hintDay; var endSameDay = hintDay && endAt && localDateString(new Date(endAt)) === hintDay; var fullName = item.full_name != null ? item.full_name : item.fullName; - var parts = [fullName]; + var timePrefix = ""; if (idx === 0) { - if (startSameDay && startHHMM) { - parts.push("с " + startHHMM); - if (endSameDay && endHHMM && endHHMM !== startHHMM) { parts.push("до " + endHHMM); } + if (dutyItems.length === 1 && startSameDay && startHHMM) { + timePrefix = "с" + nbsp + startHHMM; + if (endSameDay && endHHMM && endHHMM !== startHHMM) { timePrefix += " до" + nbsp + endHHMM; } } else if (endHHMM) { - parts.push("до " + endHHMM); + timePrefix = "до" + nbsp + endHHMM; } } else if (idx > 0) { - if (startHHMM) { parts.push("с " + startHHMM); } - if (endHHMM && endSameDay && endHHMM !== startHHMM) { parts.push("до " + endHHMM); } + if (startHHMM) { timePrefix = "с" + nbsp + startHHMM; } + if (endHHMM && endSameDay && endHHMM !== startHHMM) { timePrefix += (timePrefix ? " " : "") + "до" + nbsp + endHHMM; } } - return parts.join(", "); + return timePrefix ? timePrefix + " — " + fullName : fullName; }).join("\n"); } else { body = names; @@ -462,6 +463,61 @@ return body ? label + ":\n" + body : label; } + /** Returns HTML for duty hint with aligned times and no wrap inside name, or null to use textContent. */ + function getDutyMarkerHintHtml(marker) { + var type = marker.getAttribute("data-event-type") || "duty"; + if (type !== "duty") { return null; } + var names = marker.getAttribute("data-names") || ""; + var dutyItemsRaw = marker.getAttribute("data-duty-items") || (marker.dataset && marker.dataset.dutyItems) || ""; + var dutyItems = []; + try { + if (dutyItemsRaw) { dutyItems = JSON.parse(dutyItemsRaw); } + } catch (e) { /* ignore */ } + var hasTimes = dutyItems.length > 0 && dutyItems.some(function (it) { + var start = it.start_at != null ? it.start_at : it.startAt; + var end = it.end_at != null ? it.end_at : it.endAt; + return start || end; + }); + var hintDay = marker.getAttribute("data-date") || ""; + var nbsp = "\u00a0"; + var rows; + if (dutyItems.length >= 1 && hasTimes) { + rows = dutyItems.map(function (item, idx) { + var startAt = item.start_at != null ? item.start_at : item.startAt; + var endAt = item.end_at != null ? item.end_at : item.endAt; + var endHHMM = endAt ? formatHHMM(endAt) : ""; + var startHHMM = startAt ? formatHHMM(startAt) : ""; + var startSameDay = hintDay && startAt && localDateString(new Date(startAt)) === hintDay; + var endSameDay = hintDay && endAt && localDateString(new Date(endAt)) === hintDay; + var fullName = item.full_name != null ? item.full_name : item.fullName; + var timePrefix = ""; + if (idx === 0) { + if (dutyItems.length === 1 && startSameDay && startHHMM) { + timePrefix = "с" + nbsp + startHHMM; + if (endSameDay && endHHMM && endHHMM !== startHHMM) { timePrefix += " до" + nbsp + endHHMM; } + } else if (endHHMM) { + timePrefix = "до" + nbsp + endHHMM; + } + } else if (idx > 0) { + if (startHHMM) { timePrefix = "с" + nbsp + startHHMM; } + if (endHHMM && endSameDay && endHHMM !== startHHMM) { timePrefix += (timePrefix ? " " : "") + "до" + nbsp + endHHMM; } + } + var timeHtml = timePrefix ? escapeHtml(timePrefix) : ""; + var nameHtml = escapeHtml(fullName); + var namePart = timePrefix ? " — " + nameHtml : nameHtml; + return "" + timeHtml + "" + namePart + ""; + }); + } else { + rows = (names ? names.split("\n") : []).map(function (fullName) { + var nameHtml = escapeHtml(fullName.trim()); + return "" + nameHtml + ""; + }); + } + if (rows.length === 0) { return null; } + return "
Дежурство:
" + + rows.map(function (r) { return "
" + r + "
"; }).join("") + "
"; + } + function clearActiveDutyMarker() { calendarEl.querySelectorAll(".duty-marker.calendar-marker-active, .unavailable-marker.calendar-marker-active, .vacation-marker.calendar-marker-active") .forEach(function (m) { m.classList.remove("calendar-marker-active"); }); @@ -485,7 +541,12 @@ clearTimeout(hideTimeout); hideTimeout = null; } - hintEl.textContent = getDutyMarkerHintContent(marker); + var html = getDutyMarkerHintHtml(marker); + if (html) { + hintEl.innerHTML = html; + } else { + hintEl.textContent = getDutyMarkerHintContent(marker); + } var rect = marker.getBoundingClientRect(); positionHint(hintEl, rect); hintEl.hidden = false; @@ -506,7 +567,12 @@ return; } clearActiveDutyMarker(); - hintEl.textContent = getDutyMarkerHintContent(marker); + var html = getDutyMarkerHintHtml(marker); + if (html) { + hintEl.innerHTML = html; + } else { + hintEl.textContent = getDutyMarkerHintContent(marker); + } var rect = marker.getBoundingClientRect(); positionHint(hintEl, rect); hintEl.hidden = false; diff --git a/webapp/style.css b/webapp/style.css index 3851455..d5bd9f0 100644 --- a/webapp/style.css +++ b/webapp/style.css @@ -70,6 +70,7 @@ body { max-width: 420px; margin: 0 auto; padding: 12px; + padding-top: 0px; padding-bottom: env(safe-area-inset-bottom, 12px); } @@ -131,17 +132,6 @@ body { touch-action: pan-y; } -.calendar-sticky.is-scrolled { - box-shadow: 0 4px 6px -4px rgba(0, 0, 0, 0.25); -} - -[data-theme="light"] .calendar-sticky.is-scrolled { - box-shadow: 0 4px 6px -4px rgba(0, 0, 0, 0.08); -} - -[data-theme="dark"] .calendar-sticky.is-scrolled { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); -} .calendar { display: grid; @@ -228,7 +218,7 @@ body { position: fixed; z-index: 1000; width: max-content; - max-width: min(280px, calc(100vw - 24px)); + max-width: min(90vw, 600px); padding: 8px 12px; background: var(--surface); color: var(--text); @@ -236,15 +226,49 @@ body { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); font-size: 0.85rem; line-height: 1.4; - white-space: pre-wrap; - word-break: break-word; + white-space: pre; + overflow: visible; transform: translateY(-100%); } +.calendar-event-hint .calendar-event-hint-rows { + white-space: normal; +} + .calendar-event-hint.below { transform: none; } +.calendar-event-hint-title { + margin-bottom: 4px; + font-weight: 600; +} + +.calendar-event-hint-rows { + display: flex; + flex-direction: column; + gap: 2px; +} + +.calendar-event-hint-row { + display: table; + width: max-content; + white-space: nowrap; +} + +.calendar-event-hint-row .calendar-event-hint-time { + display: table-cell; + white-space: nowrap; + min-width: 8.5em; + vertical-align: top; + padding-right: 0.35em; +} + +.calendar-event-hint-row .calendar-event-hint-name { + display: table-cell; + white-space: nowrap !important; +} + /* Маркеры: компактный размер 11px, кнопки для доступности и тапа */ .duty-marker, .unavailable-marker,