feat: enhance current duty display with remaining time and improved contact links
All checks were successful
CI / lint-and-test (push) Successful in 35s
Docker Build and Release / build-and-push (push) Successful in 51s
Docker Build and Release / release (push) Successful in 8s

- 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:
2026-03-02 19:04:30 +03:00
parent 2e78b3c1e6
commit 0d28123d0b
7 changed files with 376 additions and 43 deletions

View File

@@ -10,6 +10,7 @@ vi.mock("./api.js", () => ({
import {
findCurrentDuty,
getRemainingTime,
renderCurrentDutyContent
} from "./currentDuty.js";
@@ -24,6 +25,28 @@ describe("currentDuty", () => {
"</div>";
});
describe("getRemainingTime", () => {
it("returns hours and minutes until end from now", () => {
const endAt = "2025-03-02T17:30:00.000Z";
vi.useFakeTimers();
vi.setSystemTime(new Date("2025-03-02T12:00:00.000Z"));
const { hours, minutes } = getRemainingTime(endAt);
vi.useRealTimers();
expect(hours).toBe(5);
expect(minutes).toBe(30);
});
it("returns 0 when end is in the past", () => {
const endAt = "2025-03-02T09:00:00.000Z";
vi.useFakeTimers();
vi.setSystemTime(new Date("2025-03-02T12:00:00.000Z"));
const { hours, minutes } = getRemainingTime(endAt);
vi.useRealTimers();
expect(hours).toBe(0);
expect(minutes).toBe(0);
});
});
describe("findCurrentDuty", () => {
it("returns duty when now is between start_at and end_at", () => {
const now = new Date();
@@ -67,13 +90,18 @@ describe("currentDuty", () => {
it("renders no-duty message and back button when duty is null", () => {
const html = renderCurrentDutyContent(null, "en");
expect(html).toContain("current-duty-card");
expect(html).toContain("current-duty-card--no-duty");
expect(html).toContain("Current Duty");
expect(html).toContain("current-duty-no-duty-wrap");
expect(html).toContain("current-duty-no-duty-icon");
expect(html).toContain("current-duty-no-duty");
expect(html).toContain("No one is on duty right now");
expect(html).toContain("Back to calendar");
expect(html).toContain('data-action="back"');
expect(html).not.toContain("current-duty-live-dot");
});
it("renders duty card with name, shift, and back button when duty has no contacts", () => {
it("renders duty card with name, shift, remaining time, and back button when duty has no contacts", () => {
const duty = {
event_type: "duty",
full_name: "Иванов Иван",
@@ -81,9 +109,12 @@ describe("currentDuty", () => {
end_at: "2025-03-03T06:00:00.000Z"
};
const html = renderCurrentDutyContent(duty, "ru");
expect(html).toContain("current-duty-live-dot");
expect(html).toContain("Текущее дежурство");
expect(html).toContain("Иванов Иван");
expect(html).toContain("Смена");
expect(html).toContain("current-duty-remaining");
expect(html).toMatch(/Осталось:\s*\d+ч\s*\d+мин/);
expect(html).toContain("Назад к календарю");
expect(html).toContain('data-action="back"');
});
@@ -99,7 +130,11 @@ describe("currentDuty", () => {
};
const html = renderCurrentDutyContent(duty, "en");
expect(html).toContain("Alice");
expect(html).toContain("current-duty-remaining");
expect(html).toMatch(/Remaining:\s*\d+h\s*\d+min/);
expect(html).toContain("current-duty-contact-row");
expect(html).toContain("current-duty-contact-row--blocks");
expect(html).toContain("current-duty-contact-block");
expect(html).toContain('href="tel:');
expect(html).toContain("+7 900 123-45-67");
expect(html).toContain("https://t.me/");