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:
@@ -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) {
|
||||
|
||||
@@ -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": "Пн",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user