Enhance duty timeline rendering and styling
All checks were successful
CI / lint-and-test (push) Successful in 15s

- Updated the HTML structure for duty timeline rows to include a date cell with a visual track, improving the layout and user experience.
- Modified CSS to define new variables for timeline date and track widths, enhancing flexibility in styling.
- Improved the styling of duty timeline elements, including today’s duties, to provide clearer visual cues and better alignment.
- Ensured consistent display of date labels and added visual enhancements for better accessibility and readability.
This commit is contained in:
2026-02-18 14:53:11 +03:00
parent 263c2fefbd
commit 1a0c49967e
2 changed files with 119 additions and 9 deletions

View File

@@ -520,17 +520,20 @@
dates.forEach(function (date) {
const isToday = date === todayKey;
const dayClass = "duty-timeline-day" + (isToday ? " duty-timeline-day--today" : "");
const dateLabel = isToday ? "Сегодня, " + dateKeyToDDMM(date) : dateKeyToDDMM(date);
const dateLabel = isToday ? dateKeyToDDMM(date) : dateKeyToDDMM(date);
const dateCellHtml = isToday
? "<span class=\"duty-timeline-date\"><span class=\"duty-timeline-date-label\">Сегодня</span><span class=\"duty-timeline-date-dot\" aria-hidden=\"true\"></span><span class=\"duty-timeline-date-day\">" + escapeHtml(dateLabel) + "</span></span>"
: "<span class=\"duty-timeline-date\">" + escapeHtml(dateLabel) + "</span>";
const dayDuties = duties.filter(function (d) { return localDateString(new Date(d.start_at)) === date; }).sort(function (a, b) { return new Date(a.start_at) - new Date(b.start_at); });
let dayHtml = "";
dayDuties.forEach(function (d) {
const start = new Date(d.start_at);
const end = new Date(d.end_at);
const isCurrent = isToday && start <= now && now < end;
dayHtml += "<div class=\"duty-timeline-row\"><span class=\"duty-timeline-date\">" + escapeHtml(dateLabel) + "</span><div class=\"duty-timeline-card-wrap\">" + dutyTimelineCardHtml(d, isCurrent) + "</div></div>";
dayHtml += "<div class=\"duty-timeline-row\">" + dateCellHtml + "<span class=\"duty-timeline-track\" aria-hidden=\"true\"></span><div class=\"duty-timeline-card-wrap\">" + dutyTimelineCardHtml(d, isCurrent) + "</div></div>";
});
if (dayDuties.length === 0 && isToday) {
dayHtml += "<div class=\"duty-timeline-row duty-timeline-row--empty\"><span class=\"duty-timeline-date\">" + escapeHtml(dateLabel) + "</span><div class=\"duty-timeline-card-wrap\"></div></div>";
dayHtml += "<div class=\"duty-timeline-row duty-timeline-row--empty\">" + dateCellHtml + "<span class=\"duty-timeline-track\" aria-hidden=\"true\"></span><div class=\"duty-timeline-card-wrap\"></div></div>";
}
fullHtml += "<div class=\"" + dayClass + "\" data-date=\"" + escapeHtml(date) + "\">" + dayHtml + "</div>";
});

View File

@@ -9,6 +9,8 @@
--unavailable: #e0af68;
--vacation: #7dcfff;
--error: #f7768e;
--timeline-date-width: 3.6em;
--timeline-track-width: 10px;
}
/* Light theme: prefer Telegram themeParams (--tg-theme-*), fallback to Telegram-like palette */
@@ -285,11 +287,20 @@ body {
vertical-align: middle;
}
/* Timeline: dates left, cards right */
/* Timeline: dates | track (line + dot) | cards */
.duty-list.duty-timeline {
border-left: 2px solid var(--muted);
padding-left: 0;
margin-left: 2px;
position: relative;
}
.duty-list.duty-timeline::before {
content: "";
position: absolute;
left: calc(var(--timeline-date-width) + var(--timeline-track-width) / 2 - 1px);
top: 0;
bottom: 0;
width: 2px;
background: var(--muted);
pointer-events: none;
}
.duty-timeline-day {
@@ -302,25 +313,121 @@ body {
.duty-timeline-row {
display: grid;
grid-template-columns: 4.2em 1fr;
gap: 0 10px;
grid-template-columns: var(--timeline-date-width) var(--timeline-track-width) 1fr;
gap: 0 4px;
align-items: start;
margin-bottom: 8px;
min-height: 1px;
}
.duty-timeline-date {
position: relative;
font-size: 0.8rem;
color: var(--muted);
padding-top: 10px;
padding-bottom: 10px;
flex-shrink: 0;
overflow: visible;
}
.duty-timeline-date::before {
content: "";
position: absolute;
left: 0;
bottom: 4px;
width: calc(100% + var(--timeline-track-width) / 2);
height: 2px;
background: linear-gradient(
to right,
color-mix(in srgb, var(--muted) 40%, transparent) 0%,
color-mix(in srgb, var(--muted) 40%, transparent) 50%,
var(--muted) 70%,
var(--muted) 100%
);
}
.duty-timeline-date::after {
content: "";
position: absolute;
left: calc(100% + (var(--timeline-track-width) / 2) - 1px);
bottom: 2px;
width: 2px;
height: 6px;
background: var(--muted);
}
.duty-timeline-day--today .duty-timeline-date {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-top: 4px;
color: var(--today);
font-weight: 600;
}
.duty-timeline-day--today .duty-timeline-date::before,
.duty-timeline-day--today .duty-timeline-date::after {
display: none;
}
.duty-timeline-date-label,
.duty-timeline-date-day {
display: block;
line-height: 1.25;
}
.duty-timeline-date-day {
align-self: flex-start;
text-align: left;
padding-left: 0;
margin-left: 0;
}
.duty-timeline-date-dot {
display: block;
width: 100%;
height: 8px;
min-height: 8px;
position: relative;
flex-shrink: 0;
}
.duty-timeline-date-dot::before {
content: "";
position: absolute;
left: 0;
top: 50%;
margin-top: -1px;
width: calc(100% + var(--timeline-track-width) / 2);
height: 1px;
background: color-mix(in srgb, var(--today) 45%, transparent);
}
.duty-timeline-date-dot::after {
content: "";
position: absolute;
left: calc(100% + (var(--timeline-track-width) / 2) - 1px);
top: 50%;
margin-top: -3px;
width: 2px;
height: 6px;
background: var(--today);
}
.duty-timeline-day--today .duty-timeline-date .duty-timeline-date-label {
color: var(--today);
}
.duty-timeline-day--today .duty-timeline-date .duty-timeline-date-day {
color: var(--muted);
font-weight: 400;
font-size: 0.75rem;
}
.duty-timeline-track {
min-width: 0;
}
.duty-timeline-card-wrap {
min-width: 0;
}