- 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.
112 lines
3.7 KiB
JavaScript
112 lines
3.7 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|