feat: enhance current duty display with remaining time and improved contact links
- Added a new message key for displaying remaining time until the end of the shift in both English and Russian. - Updated the current duty card to show remaining time with a formatted string. - Enhanced the contact links to support block layout with icons for phone and Telegram, improving visual presentation. - Implemented a new utility function to calculate remaining time until the end of the shift. - Added unit tests for the new functionality, ensuring accurate time calculations and proper rendering of contact links.
This commit is contained in:
@@ -14,11 +14,29 @@ import {
|
||||
formatHHMM
|
||||
} from "./dateUtils.js";
|
||||
|
||||
/** Empty calendar icon for "no duty" state (outline, stroke). */
|
||||
const ICON_NO_DUTY =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>';
|
||||
|
||||
/** @type {(() => void)|null} Callback when user taps "Back to calendar". */
|
||||
let onBackCallback = null;
|
||||
/** @type {(() => void)|null} Handler registered with Telegram BackButton.onClick. */
|
||||
let backButtonHandler = null;
|
||||
|
||||
/**
|
||||
* Compute remaining time until end of shift. Call only when now < end (active duty).
|
||||
* @param {string|Date} endAt - ISO end time of the shift
|
||||
* @returns {{ hours: number, minutes: number }}
|
||||
*/
|
||||
export function getRemainingTime(endAt) {
|
||||
const end = new Date(endAt).getTime();
|
||||
const now = Date.now();
|
||||
const ms = Math.max(0, end - now);
|
||||
const hours = Math.floor(ms / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
|
||||
return { hours, minutes };
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the duty that is currently active (start <= now < end). Prefer event_type === "duty".
|
||||
* @param {object[]} duties - List of duties with start_at, end_at, event_type
|
||||
@@ -49,13 +67,18 @@ export function renderCurrentDutyContent(duty, lang) {
|
||||
if (!duty) {
|
||||
const noDuty = t(lang, "current_duty.no_duty");
|
||||
return (
|
||||
'<div class="current-duty-card">' +
|
||||
'<div class="current-duty-card current-duty-card--no-duty">' +
|
||||
'<h2 class="current-duty-title">' +
|
||||
escapeHtml(title) +
|
||||
"</h2>" +
|
||||
'<div class="current-duty-no-duty-wrap">' +
|
||||
'<span class="current-duty-no-duty-icon">' +
|
||||
ICON_NO_DUTY +
|
||||
"</span>" +
|
||||
'<p class="current-duty-no-duty">' +
|
||||
escapeHtml(noDuty) +
|
||||
"</p>" +
|
||||
"</div>" +
|
||||
'<button type="button" class="current-duty-back-btn" data-action="back">' +
|
||||
escapeHtml(backLabel) +
|
||||
"</button>" +
|
||||
@@ -78,15 +101,22 @@ export function renderCurrentDutyContent(duty, lang) {
|
||||
" " +
|
||||
endTime;
|
||||
const shiftLabel = t(lang, "current_duty.shift");
|
||||
const { hours: remHours, minutes: remMinutes } = getRemainingTime(duty.end_at);
|
||||
const remainingStr = t(lang, "current_duty.remaining", {
|
||||
hours: String(remHours),
|
||||
minutes: String(remMinutes)
|
||||
});
|
||||
const contactHtml = buildContactLinksHtml(lang, duty.phone, duty.username, {
|
||||
classPrefix: "current-duty-contact",
|
||||
showLabels: true,
|
||||
separator: " "
|
||||
separator: " ",
|
||||
layout: "block"
|
||||
});
|
||||
|
||||
return (
|
||||
'<div class="current-duty-card">' +
|
||||
'<h2 class="current-duty-title">' +
|
||||
'<span class="current-duty-live-dot"></span> ' +
|
||||
escapeHtml(title) +
|
||||
"</h2>" +
|
||||
'<p class="current-duty-name">' +
|
||||
@@ -97,6 +127,9 @@ export function renderCurrentDutyContent(duty, lang) {
|
||||
": " +
|
||||
escapeHtml(shiftStr) +
|
||||
"</div>" +
|
||||
'<div class="current-duty-remaining">' +
|
||||
escapeHtml(remainingStr) +
|
||||
"</div>" +
|
||||
contactHtml +
|
||||
'<button type="button" class="current-duty-back-btn" data-action="back">' +
|
||||
escapeHtml(backLabel) +
|
||||
|
||||
Reference in New Issue
Block a user