Files
miem_workers/app/static/admin.js

154 lines
6.1 KiB
JavaScript

(function () {
const columnDefaults = [
"full_name",
"status",
"positions",
"last_seen_at",
"profile",
];
const storageKey = "miem.directory.columns";
const columnPresets = {
review: ["full_name", "status", "academic_degree", "last_seen_at", "profile"],
contacts: ["full_name", "status", "email", "phone", "address", "profile"],
full: ["full_name", "status", "positions", "hse_start_year", "email", "phone", "address", "academic_degree", "publications_count", "courses_count", "news_count", "first_seen_at", "last_seen_at", "dismissed_at", "profile"],
};
function readColumns() {
try {
const stored = JSON.parse(localStorage.getItem(storageKey) || "[]");
return Array.isArray(stored) && stored.length ? stored : columnDefaults;
} catch (_error) {
return columnDefaults;
}
}
function writeColumns(columns) {
localStorage.setItem(storageKey, JSON.stringify(columns));
}
function applyColumns(columns) {
document.querySelectorAll("[data-column]").forEach((node) => {
const visible = columns.includes(node.dataset.column);
node.classList.toggle("directory-table__cell--hidden", !visible && node.classList.contains("directory-table__cell"));
node.classList.toggle("directory-table__head--hidden", !visible && node.classList.contains("directory-table__head"));
});
document.querySelectorAll("[data-column-toggle]").forEach((checkbox) => {
checkbox.checked = columns.includes(checkbox.value);
});
}
function setupColumns() {
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);
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.close();
});
});
document.querySelectorAll("[data-columns-preset]").forEach((button) => {
button.addEventListener("click", () => {
columns = columnPresets[button.dataset.columnsPreset];
writeColumns(columns);
applyColumns(columns);
});
});
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);
if (!columns.length) columns = ["full_name"];
writeColumns(columns);
applyColumns(columns);
});
});
}
function setupProgress() {
const panel = document.querySelector("[data-progress-panel]");
if (!panel) return;
const state = document.createElement("p");
state.className = "progress-panel__empty";
state.setAttribute("role", "status");
panel.append(state);
let pending = false;
const update = (run) => {
if (!run) return;
const status = document.querySelector("[data-progress-status]");
const processed = document.querySelector("[data-progress-processed]");
const found = document.querySelector("[data-progress-found]");
const skipped = document.querySelector("[data-progress-skipped]");
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;
if (skipped) skipped.textContent = run.skipped_count;
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 () => {
if (pending) return true;
pending = true;
state.textContent = "Обновляем прогресс…";
panel.setAttribute("aria-busy", "true");
try {
const response = await fetch("/api/crawl-runs/latest", {
credentials: "same-origin",
signal: AbortSignal.timeout(15000),
});
if (!response.ok) throw new Error("progress request failed");
const data = await response.json();
const run = data.running || data.latest;
update(run);
state.textContent = run ? "Прогресс обновлён" : "Запусков пока нет. Запустите парсинг.";
const error = document.querySelector("[data-progress-error]");
if (error) error.hidden = true;
return Boolean(data.running);
} catch (_error) {
const error = document.querySelector("[data-progress-error]");
if (error) error.hidden = false;
state.textContent = "Показаны последние полученные данные. Прогресс может быть устаревшим.";
return true;
} finally {
pending = false;
panel.setAttribute("aria-busy", "false");
}
};
document.querySelectorAll("[data-progress-retry]").forEach((button) => button.addEventListener("click", poll));
const interval = window.setInterval(async () => {
const keepGoing = await poll();
if (!keepGoing) window.clearInterval(interval);
}, 4000);
}
setupColumns();
setupProgress();
})();