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.
This commit is contained in:
2026-03-02 20:21:33 +03:00
parent 37d4226beb
commit 54446d7b0f
12 changed files with 154 additions and 19 deletions

View File

@@ -339,10 +339,14 @@ function ensurePanelInDom() {
panelEl.hidden = true;
const closeLabel = t(state.lang, "day_detail.close");
const closeIcon =
'<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
panelEl.innerHTML =
'<button type="button" class="day-detail-close" aria-label="' +
escapeHtml(closeLabel) +
'">×</button><div class="day-detail-body"></div>';
'">' +
closeIcon +
'</button><div class="day-detail-body"></div>';
const closeBtn = panelEl.querySelector(".day-detail-close");
if (closeBtn) {

View File

@@ -13,6 +13,7 @@ export const MESSAGES = {
error_load_failed: "Load failed",
error_network: "Could not load data. Check your connection.",
error_generic: "Could not load data.",
"error.retry": "Retry",
"nav.prev_month": "Previous month",
"nav.next_month": "Next month",
"weekdays.mon": "Mon",
@@ -68,6 +69,7 @@ export const MESSAGES = {
error_load_failed: "Ошибка загрузки",
error_network: "Не удалось загрузить данные. Проверьте интернет.",
error_generic: "Не удалось загрузить данные.",
"error.retry": "Повторить",
"nav.prev_month": "Предыдущий месяц",
"nav.next_month": "Следующий месяц",
"weekdays.mon": "Пн",

View File

@@ -189,7 +189,7 @@ async function loadMonth() {
}
return;
}
showError(e.message || t(state.lang, "error_generic"));
showError(e.message || t(state.lang, "error_generic"), loadMonth);
setNavEnabled(true);
return;
}

View File

@@ -15,6 +15,7 @@ import {
getNextBtn
} from "./dom.js";
import { t } from "./i18n.js";
import { escapeHtml } from "./utils.js";
/**
* Show access-denied view and hide calendar/list/loading/error.
@@ -63,16 +64,37 @@ export function hideAccessDenied() {
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) {
export function showError(msg, onRetry) {
const errorEl = getErrorEl();
const loadingEl = getLoadingEl();
if (errorEl) {
errorEl.textContent = msg;
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");
}

View File

@@ -93,7 +93,8 @@ describe("ui", () => {
it("sets error text and shows error element", () => {
showError("Network error");
const errorEl = document.getElementById("error");
expect(errorEl?.textContent).toBe("Network error");
const textEl = errorEl?.querySelector(".error-text");
expect(textEl?.textContent).toBe("Network error");
expect(errorEl?.hidden).toBe(false);
});