feat: enhance CI workflow and update webapp styles
Some checks failed
CI / lint-and-test (push) Failing after 45s
Some checks failed
CI / lint-and-test (push) Failing after 45s
- Added Node.js setup and webapp testing steps to the CI workflow for improved integration. - Updated HTML to link multiple CSS files for better modularity and organization of styles. - Removed deprecated `style.css` and introduced new CSS files for base styles, calendar, day detail, hints, markers, states, and duty list to enhance maintainability and readability. - Implemented new styles for improved presentation of duty information and user interactions. - Added unit tests for new API functions and contact link rendering to ensure functionality and reliability.
This commit is contained in:
@@ -3,9 +3,10 @@
|
||||
* Fetches today's duties, finds the active one (start <= now < end), shows name, shift, contacts.
|
||||
*/
|
||||
|
||||
import { currentDutyViewEl, state, loadingEl } from "./dom.js";
|
||||
import { getCurrentDutyViewEl, state, getLoadingEl } from "./dom.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { escapeHtml } from "./utils.js";
|
||||
import { buildContactLinksHtml } from "./contactHtml.js";
|
||||
import { fetchDuties } from "./api.js";
|
||||
import {
|
||||
localDateString,
|
||||
@@ -13,6 +14,11 @@ import {
|
||||
formatHHMM
|
||||
} from "./dateUtils.js";
|
||||
|
||||
/** @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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -30,54 +36,6 @@ export function findCurrentDuty(duties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build contact HTML (phone + Telegram) for current duty card, styled like day-detail.
|
||||
* @param {'ru'|'en'} lang
|
||||
* @param {object} d - Duty with optional phone, username
|
||||
* @returns {string}
|
||||
*/
|
||||
function buildContactHtml(lang, d) {
|
||||
const parts = [];
|
||||
if (d.phone && String(d.phone).trim()) {
|
||||
const p = String(d.phone).trim();
|
||||
const label = t(lang, "contact.phone");
|
||||
const safeHref =
|
||||
"tel:" +
|
||||
p.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
||||
parts.push(
|
||||
'<span class="current-duty-contact">' +
|
||||
escapeHtml(label) +
|
||||
": " +
|
||||
'<a href="' +
|
||||
safeHref +
|
||||
'" class="current-duty-contact-link current-duty-contact-phone">' +
|
||||
escapeHtml(p) +
|
||||
"</a></span>"
|
||||
);
|
||||
}
|
||||
if (d.username && String(d.username).trim()) {
|
||||
const u = String(d.username).trim().replace(/^@+/, "");
|
||||
if (u) {
|
||||
const label = t(lang, "contact.telegram");
|
||||
const display = "@" + u;
|
||||
const href = "https://t.me/" + encodeURIComponent(u);
|
||||
parts.push(
|
||||
'<span class="current-duty-contact">' +
|
||||
escapeHtml(label) +
|
||||
": " +
|
||||
'<a href="' +
|
||||
escapeHtml(href) +
|
||||
'" class="current-duty-contact-link current-duty-contact-username" target="_blank" rel="noopener noreferrer">' +
|
||||
escapeHtml(display) +
|
||||
"</a></span>"
|
||||
);
|
||||
}
|
||||
}
|
||||
return parts.length
|
||||
? '<div class="current-duty-contact-row">' + parts.join(" ") + "</div>"
|
||||
: "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the current duty view content (card with duty or no-duty message).
|
||||
* @param {object|null} duty - Active duty or null
|
||||
@@ -120,7 +78,11 @@ export function renderCurrentDutyContent(duty, lang) {
|
||||
" " +
|
||||
endTime;
|
||||
const shiftLabel = t(lang, "current_duty.shift");
|
||||
const contactHtml = buildContactHtml(lang, duty);
|
||||
const contactHtml = buildContactLinksHtml(lang, duty.phone, duty.username, {
|
||||
classPrefix: "current-duty-contact",
|
||||
showLabels: true,
|
||||
separator: " "
|
||||
});
|
||||
|
||||
return (
|
||||
'<div class="current-duty-card">' +
|
||||
@@ -149,16 +111,18 @@ export function renderCurrentDutyContent(duty, lang) {
|
||||
* @param {() => void} onBack - Callback when user taps "Back to calendar"
|
||||
*/
|
||||
export async function showCurrentDutyView(onBack) {
|
||||
const currentDutyViewEl = getCurrentDutyViewEl();
|
||||
const container = currentDutyViewEl && currentDutyViewEl.closest(".container");
|
||||
const calendarSticky = document.getElementById("calendarSticky");
|
||||
const dutyList = document.getElementById("dutyList");
|
||||
if (!currentDutyViewEl) return;
|
||||
|
||||
currentDutyViewEl._onBack = onBack;
|
||||
onBackCallback = onBack;
|
||||
currentDutyViewEl.classList.remove("hidden");
|
||||
if (container) container.setAttribute("data-view", "currentDuty");
|
||||
if (calendarSticky) calendarSticky.hidden = true;
|
||||
if (dutyList) dutyList.hidden = true;
|
||||
const loadingEl = getLoadingEl();
|
||||
if (loadingEl) loadingEl.classList.add("hidden");
|
||||
|
||||
const lang = state.lang;
|
||||
@@ -170,9 +134,9 @@ export async function showCurrentDutyView(onBack) {
|
||||
if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.BackButton) {
|
||||
window.Telegram.WebApp.BackButton.show();
|
||||
const handler = () => {
|
||||
if (currentDutyViewEl._onBack) currentDutyViewEl._onBack();
|
||||
if (onBackCallback) onBackCallback();
|
||||
};
|
||||
currentDutyViewEl._backButtonHandler = handler;
|
||||
backButtonHandler = handler;
|
||||
window.Telegram.WebApp.BackButton.onClick(handler);
|
||||
}
|
||||
|
||||
@@ -205,9 +169,7 @@ export async function showCurrentDutyView(onBack) {
|
||||
function handleCurrentDutyClick(e) {
|
||||
const btn = e.target && e.target.closest("[data-action='back']");
|
||||
if (!btn) return;
|
||||
if (currentDutyViewEl && currentDutyViewEl._onBack) {
|
||||
currentDutyViewEl._onBack();
|
||||
}
|
||||
if (onBackCallback) onBackCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,24 +177,24 @@ function handleCurrentDutyClick(e) {
|
||||
* Hides Telegram BackButton and calls loadMonth so calendar is populated.
|
||||
*/
|
||||
export function hideCurrentDutyView() {
|
||||
const currentDutyViewEl = getCurrentDutyViewEl();
|
||||
const container = currentDutyViewEl && currentDutyViewEl.closest(".container");
|
||||
const calendarSticky = document.getElementById("calendarSticky");
|
||||
const dutyList = document.getElementById("dutyList");
|
||||
const backHandler = currentDutyViewEl && currentDutyViewEl._backButtonHandler;
|
||||
|
||||
if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.BackButton) {
|
||||
if (backHandler) {
|
||||
window.Telegram.WebApp.BackButton.offClick(backHandler);
|
||||
if (backButtonHandler) {
|
||||
window.Telegram.WebApp.BackButton.offClick(backButtonHandler);
|
||||
}
|
||||
window.Telegram.WebApp.BackButton.hide();
|
||||
}
|
||||
if (currentDutyViewEl) {
|
||||
currentDutyViewEl.removeEventListener("click", handleCurrentDutyClick);
|
||||
currentDutyViewEl._onBack = null;
|
||||
currentDutyViewEl._backButtonHandler = null;
|
||||
currentDutyViewEl.classList.add("hidden");
|
||||
currentDutyViewEl.innerHTML = "";
|
||||
}
|
||||
onBackCallback = null;
|
||||
backButtonHandler = null;
|
||||
if (container) container.removeAttribute("data-view");
|
||||
if (calendarSticky) calendarSticky.hidden = false;
|
||||
if (dutyList) dutyList.hidden = false;
|
||||
|
||||
Reference in New Issue
Block a user