/** * Current duty view: full-screen card when opened via Mini App deep link (startapp=duty). * Fetches today's duties, finds the active one (start <= now < end), shows name, shift, contacts. */ import { currentDutyViewEl, state, loadingEl } from "./dom.js"; import { t } from "./i18n.js"; import { escapeHtml } from "./utils.js"; import { fetchDuties } from "./api.js"; import { localDateString, dateKeyToDDMM, formatHHMM } from "./dateUtils.js"; /** * 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 * @returns {object|null} */ export function findCurrentDuty(duties) { const now = Date.now(); const dutyType = (duties || []).filter((d) => d.event_type === "duty"); const candidates = dutyType.length ? dutyType : duties || []; for (const d of candidates) { const start = new Date(d.start_at).getTime(); const end = new Date(d.end_at).getTime(); if (start <= now && now < end) return d; } 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(/' + escapeHtml(label) + ": " + '' + escapeHtml(p) + "" ); } 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( '' + escapeHtml(label) + ": " + '' + escapeHtml(display) + "" ); } } return parts.length ? '
' + parts.join(" ") + "
" : ""; } /** * Render the current duty view content (card with duty or no-duty message). * @param {object|null} duty - Active duty or null * @param {string} lang * @returns {string} */ export function renderCurrentDutyContent(duty, lang) { const backLabel = t(lang, "current_duty.back"); const title = t(lang, "current_duty.title"); if (!duty) { const noDuty = t(lang, "current_duty.no_duty"); return ( '
' + '

' + escapeHtml(title) + "

" + '

' + escapeHtml(noDuty) + "

" + '" + "
" ); } const startLocal = localDateString(new Date(duty.start_at)); const endLocal = localDateString(new Date(duty.end_at)); const startDDMM = dateKeyToDDMM(startLocal); const endDDMM = dateKeyToDDMM(endLocal); const startTime = formatHHMM(duty.start_at); const endTime = formatHHMM(duty.end_at); const shiftStr = startDDMM + " " + startTime + " — " + endDDMM + " " + endTime; const shiftLabel = t(lang, "current_duty.shift"); const contactHtml = buildContactHtml(lang, duty); return ( '
' + '

' + escapeHtml(title) + "

" + '

' + escapeHtml(duty.full_name) + "

" + '
' + escapeHtml(shiftLabel) + ": " + escapeHtml(shiftStr) + "
" + contactHtml + '" + "
" ); } /** * Show the current duty view: fetch today's duties, render card or no-duty, show back button. * Hides calendar/duty list and shows #currentDutyView. Optionally shows Telegram BackButton. * @param {() => void} onBack - Callback when user taps "Back to calendar" */ export async function showCurrentDutyView(onBack) { const container = currentDutyViewEl && currentDutyViewEl.closest(".container"); const calendarSticky = document.getElementById("calendarSticky"); const dutyList = document.getElementById("dutyList"); if (!currentDutyViewEl) return; currentDutyViewEl._onBack = onBack; currentDutyViewEl.classList.remove("hidden"); if (container) container.setAttribute("data-view", "currentDuty"); if (calendarSticky) calendarSticky.hidden = true; if (dutyList) dutyList.hidden = true; if (loadingEl) loadingEl.classList.add("hidden"); const lang = state.lang; currentDutyViewEl.innerHTML = '
' + escapeHtml(t(lang, "loading")) + "
"; if (window.Telegram && window.Telegram.WebApp && window.Telegram.WebApp.BackButton) { window.Telegram.WebApp.BackButton.show(); const handler = () => { if (currentDutyViewEl._onBack) currentDutyViewEl._onBack(); }; currentDutyViewEl._backButtonHandler = handler; window.Telegram.WebApp.BackButton.onClick(handler); } const today = new Date(); const from = localDateString(today); const to = from; try { const duties = await fetchDuties(from, to); const duty = findCurrentDuty(duties); currentDutyViewEl.innerHTML = renderCurrentDutyContent(duty, lang); } catch (e) { currentDutyViewEl.innerHTML = '
' + '

' + escapeHtml(e.message || t(lang, "error_generic")) + "

" + '" + "
"; } currentDutyViewEl.addEventListener("click", handleCurrentDutyClick); } /** * Delegate click for back button. * @param {MouseEvent} e */ function handleCurrentDutyClick(e) { const btn = e.target && e.target.closest("[data-action='back']"); if (!btn) return; if (currentDutyViewEl && currentDutyViewEl._onBack) { currentDutyViewEl._onBack(); } } /** * Hide the current duty view and show calendar/duty list again. * Hides Telegram BackButton and calls loadMonth so calendar is populated. */ export function hideCurrentDutyView() { 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); } window.Telegram.WebApp.BackButton.hide(); } if (currentDutyViewEl) { currentDutyViewEl.removeEventListener("click", handleCurrentDutyClick); currentDutyViewEl._onBack = null; currentDutyViewEl._backButtonHandler = null; currentDutyViewEl.classList.add("hidden"); currentDutyViewEl.innerHTML = ""; } if (container) container.removeAttribute("data-view"); if (calendarSticky) calendarSticky.hidden = false; if (dutyList) dutyList.hidden = false; }