diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b7e5c..a176b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.7.9 + +- Улучшены доступность, фильтры, состояния и адаптивность административного каталога. + ## 0.7.8 - В колонке учёной степени отображаются все найденные степени без лишнего текста. diff --git a/app/static/admin.css b/app/static/admin.css index 51a8dd8..26cb0ed 100644 --- a/app/static/admin.css +++ b/app/static/admin.css @@ -5,7 +5,22 @@ flex-direction: column; color: #1f2937; background: #f6f7f9; - font-family: Arial, sans-serif; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +.admin__skip-link { + position: absolute; + top: 8px; + left: 8px; + z-index: 100; + padding: 8px 12px; + color: #ffffff; + background: #0f766e; + transform: translateY(-150%); +} + +.admin__skip-link:focus { + transform: translateY(0); } .admin__header { @@ -16,6 +31,7 @@ padding: 18px 32px; background: #ffffff; border-bottom: 1px solid #d9dee7; + flex-wrap: wrap; } .admin__brand { @@ -40,6 +56,16 @@ font-weight: 700; } +.admin__link[aria-current="page"] { + text-decoration: underline; + text-underline-offset: 4px; +} + +:is(a, button, input, select):focus-visible { + outline: 3px solid #14b8a6; + outline-offset: 2px; +} + .admin__main { flex: 1; width: min(1180px, calc(100% - 32px)); @@ -161,8 +187,9 @@ border-radius: 6px; } -.button { - padding: 10px 14px; +.button { + min-height: 40px; + padding: 10px 14px; border: 0; border-radius: 6px; color: #ffffff; @@ -426,25 +453,25 @@ gap: 12px; } -.progress-panel__header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} - -.progress-panel__actions { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.button--secondary { - color: #0f766e; - background: #ccfbf1; -} - -.progress-panel__body { +.progress-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.progress-panel__actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.button--secondary { + color: #0f766e; + background: #ccfbf1; +} + +.progress-panel__body { display: grid; gap: 10px; } @@ -467,6 +494,12 @@ color: #6b7280; } +.progress-panel__error { + margin: 0; + color: #991b1b; + font-weight: 700; +} + .progress-bar { height: 12px; overflow: hidden; @@ -478,7 +511,6 @@ height: 100%; width: 0; background: #0f766e; - transition: width 0.25s ease; } .directory { @@ -505,7 +537,7 @@ .directory__filters { display: grid; - grid-template-columns: minmax(220px, 1.7fr) repeat(6, minmax(120px, 1fr)); + grid-template-columns: 1.1fr 2fr 1.25fr 2fr; gap: 10px; padding: 16px; background: #ffffff; @@ -513,6 +545,38 @@ border-radius: 8px; } +.directory__filter-group { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + align-content: start; + gap: 8px; + min-width: 0; + margin: 0; + padding: 0; + border: 0; +} + +.directory__filter-group--search { + grid-template-columns: 1fr; +} + +.directory__filter-legend { + grid-column: 1 / -1; + margin-bottom: 2px; + color: #374151; + font-size: 12px; + font-weight: 700; +} + +.directory__field { + display: grid; + gap: 5px; + min-width: 0; + color: #4b5563; + font-size: 12px; + font-weight: 700; +} + .directory__input { min-width: 0; padding: 10px 12px; @@ -520,6 +584,14 @@ border-radius: 6px; } +.directory__filter-actions { + display: flex; + align-items: end; + flex-wrap: wrap; + gap: 8px; + grid-column: 1 / -1; +} + .directory__table-wrap { overflow-x: auto; background: #ffffff; @@ -541,7 +613,7 @@ .directory-table { width: 100%; - min-width: 1120px; + min-width: 960px; border-collapse: collapse; } @@ -563,7 +635,7 @@ } .directory-table__row { - cursor: pointer; + cursor: default; } .directory-table__row:hover { @@ -576,33 +648,32 @@ text-align: center; } +.directory-table__empty span, +.directory-table__empty a { + display: block; + margin-top: 8px; +} + .directory-table__cell--hidden, .directory-table__head--hidden { display: none; } .columns-modal { - position: fixed; - inset: 0; - z-index: 50; - display: grid; - place-items: center; - padding: 20px; + width: min(620px, calc(100% - 40px)); + max-height: min(720px, calc(100vh - 40px)); + margin: auto; + padding: 0; + border: 0; + background: transparent; } -.columns-modal[hidden] { - display: none; -} - -.columns-modal__backdrop { - position: absolute; - inset: 0; +.columns-modal::backdrop { background: rgba(17, 24, 39, 0.54); } .columns-modal__panel { position: relative; - width: min(620px, 100%); max-height: min(720px, calc(100vh - 40px)); overflow: auto; padding: 20px; @@ -650,6 +721,11 @@ grid-template-columns: 1fr 1fr; } + .directory__filter-group--search, + .directory__filter-actions { + grid-column: 1 / -1; + } + .progress-panel__header, .directory__header, .employee-card__header { @@ -659,7 +735,40 @@ } @media (max-width: 620px) { + .admin__header { + align-items: flex-start; + padding: 16px; + } + + .admin__nav { + width: 100%; + flex-wrap: wrap; + } + + .admin__main { + width: min(100% - 24px, 1180px); + margin: 20px auto; + } + .directory__filters { grid-template-columns: 1fr; } + + .directory__filter-group, + .directory__filter-group--search, + .directory__filter-actions { + grid-column: 1; + } + + .directory__field { + font-size: 13px; + } +} + +@media (prefers-reduced-motion: no-preference) { + .progress-bar__fill { + transition-property: width; + transition-duration: 0.25s; + transition-timing-function: ease; + } } diff --git a/app/static/admin.js b/app/static/admin.js index dc5b1d6..79aa0af 100644 --- a/app/static/admin.js +++ b/app/static/admin.js @@ -40,18 +40,26 @@ if (!document.querySelector("[data-directory-table]")) return; let columns = readColumns(); const modal = document.querySelector("[data-columns-modal]"); + const trigger = document.querySelector("[data-columns-open]"); applyColumns(columns); - document.querySelectorAll("[data-columns-open]").forEach((button) => { - button.addEventListener("click", () => { - modal.hidden = false; - }); + trigger.addEventListener("click", () => { + modal.showModal(); + trigger.setAttribute("aria-expanded", "true"); + modal.querySelector("input")?.focus(); }); document.querySelectorAll("[data-columns-close]").forEach((button) => { button.addEventListener("click", () => { - modal.hidden = true; + modal.close(); }); }); + modal.addEventListener("click", (event) => { + if (event.target === modal) modal.close(); + }); + modal.addEventListener("close", () => { + trigger.setAttribute("aria-expanded", "false"); + trigger.focus(); + }); document.querySelectorAll("[data-column-toggle]").forEach((checkbox) => { checkbox.addEventListener("change", () => { columns = Array.from(document.querySelectorAll("[data-column-toggle]:checked")).map((item) => item.value); @@ -62,25 +70,6 @@ }); } - function setupClickableRows() { - const openRow = (row) => { - window.location.href = row.dataset.rowHref; - }; - - document.querySelectorAll("[data-row-href]").forEach((row) => { - row.addEventListener("click", (event) => { - if (event.target.closest("a, button, input, select, label")) return; - openRow(row); - }); - row.addEventListener("keydown", (event) => { - if (!["Enter", " "].includes(event.key)) return; - if (event.target.closest("a, button, input, select, label")) return; - event.preventDefault(); - openRow(row); - }); - }); - } - function setupProgress() { const panel = document.querySelector("[data-progress-panel]"); if (!panel) return; @@ -94,6 +83,7 @@ const errors = document.querySelector("[data-progress-errors]"); const fill = document.querySelector("[data-progress-fill]"); const percent = document.querySelector("[data-progress-percent]"); + const error = document.querySelector("[data-progress-error]"); if (status) status.textContent = run.status_display || run.status; if (processed) processed.textContent = run.processed_count; if (found) found.textContent = run.found_count; @@ -101,17 +91,24 @@ if (errors) errors.textContent = run.error_count; if (fill) fill.style.width = `${run.progress_percent}%`; if (percent) percent.textContent = run.progress_percent; + if (fill) { + fill.parentElement.setAttribute("aria-valuenow", run.progress_percent); + fill.parentElement.setAttribute("aria-valuetext", `${run.progress_percent}%`); + } + if (error) error.hidden = true; }; const poll = async () => { try { const response = await fetch("/api/crawl-runs/latest", { credentials: "same-origin" }); - if (!response.ok) return false; + if (!response.ok) throw new Error("progress request failed"); const data = await response.json(); const run = data.running || data.latest; update(run); return Boolean(data.running); } catch (_error) { + const error = document.querySelector("[data-progress-error]"); + if (error) error.hidden = false; return false; } }; @@ -123,6 +120,5 @@ } setupColumns(); - setupClickableRows(); setupProgress(); })(); diff --git a/app/templates/base.html b/app/templates/base.html index 8ebbe07..c7c70e7 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -7,18 +7,19 @@
+ Перейти к содержимому