Files
duty-teller/webapp/js/ui.js
Nikolay Tatarinov 54446d7b0f feat: enhance UI components and error handling
- Updated HTML structure for navigation buttons in the calendar, adding SVG icons for improved visual clarity.
- Introduced a new muted text style in CSS for better presentation of empty duty list messages.
- Enhanced calendar CSS for navigation buttons and day indicators, improving layout and responsiveness.
- Improved error handling in the UI by adding retry functionality to the error display, allowing users to retry actions directly from the error message.
- Updated internationalization messages to include a retry option for error handling.
- Added unit tests to verify the new error handling behavior and UI updates.
2026-03-02 20:21:33 +03:00

112 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Screen states: access denied, error, nav enabled.
*/
import {
state,
getCalendarEl,
getDutyListEl,
getLoadingEl,
getErrorEl,
getAccessDeniedEl,
getHeaderEl,
getWeekdaysEl,
getPrevBtn,
getNextBtn
} from "./dom.js";
import { t } from "./i18n.js";
import { escapeHtml } from "./utils.js";
/**
* Show access-denied view and hide calendar/list/loading/error.
* @param {string} [serverDetail] - message from API 403 detail (shown below main text when present)
*/
export function showAccessDenied(serverDetail) {
const headerEl = getHeaderEl();
const weekdaysEl = getWeekdaysEl();
const calendarEl = getCalendarEl();
const dutyListEl = getDutyListEl();
const loadingEl = getLoadingEl();
const errorEl = getErrorEl();
const accessDeniedEl = getAccessDeniedEl();
if (headerEl) headerEl.hidden = true;
if (weekdaysEl) weekdaysEl.hidden = true;
if (calendarEl) calendarEl.hidden = true;
if (dutyListEl) dutyListEl.hidden = true;
if (loadingEl) loadingEl.classList.add("hidden");
if (errorEl) errorEl.hidden = true;
if (accessDeniedEl) {
const msg = t(state.lang, "access_denied");
accessDeniedEl.innerHTML = "<p>" + msg + "</p>";
if (serverDetail && serverDetail.trim()) {
const detail = document.createElement("p");
detail.className = "access-denied-detail";
detail.textContent = serverDetail;
accessDeniedEl.appendChild(detail);
}
accessDeniedEl.hidden = false;
}
}
/**
* Hide access-denied and show calendar/list/header/weekdays.
*/
export function hideAccessDenied() {
const accessDeniedEl = getAccessDeniedEl();
const headerEl = getHeaderEl();
const weekdaysEl = getWeekdaysEl();
const calendarEl = getCalendarEl();
const dutyListEl = getDutyListEl();
if (accessDeniedEl) accessDeniedEl.hidden = true;
if (headerEl) headerEl.hidden = false;
if (weekdaysEl) weekdaysEl.hidden = false;
if (calendarEl) calendarEl.hidden = false;
if (dutyListEl) dutyListEl.hidden = false;
}
/** Warning icon SVG for error state (24×24). */
const ERROR_ICON_SVG =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="error-icon" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
/**
* Show error message and hide loading.
* @param {string} msg - Error text
* @param {(() => void)|null} [onRetry] - Optional callback for Retry button
*/
export function showError(msg, onRetry) {
const errorEl = getErrorEl();
const loadingEl = getLoadingEl();
if (errorEl) {
const retryLabel = t(state.lang, "error.retry");
const safeMsg = typeof msg === "string" ? msg : t(state.lang, "error_generic");
let html =
ERROR_ICON_SVG +
'<p class="error-text">' +
escapeHtml(safeMsg) +
"</p>";
if (typeof onRetry === "function") {
html += '<button type="button" class="error-retry">' + escapeHtml(retryLabel) + "</button>";
}
errorEl.innerHTML = html;
errorEl.hidden = false;
const retryBtn = errorEl.querySelector(".error-retry");
if (retryBtn && typeof onRetry === "function") {
retryBtn.addEventListener("click", () => {
onRetry();
});
}
}
if (loadingEl) loadingEl.classList.add("hidden");
}
/**
* Enable or disable prev/next month buttons.
* @param {boolean} enabled
*/
export function setNavEnabled(enabled) {
const prevBtn = getPrevBtn();
const nextBtn = getNextBtn();
if (prevBtn) prevBtn.disabled = !enabled;
if (nextBtn) nextBtn.disabled = !enabled;
}