feat: add select-all for external queue
This commit is contained in:
@@ -17,7 +17,7 @@ const apiUrl = import.meta.env.PUBLIC_API_URL || "http://localhost:8000";
|
|||||||
<label>Порядок<select name="order"><option value="risk">Сначала требуют внимания</option><option value="newest">Сначала новые</option><option value="oldest">Сначала старые</option></select></label>
|
<label>Порядок<select name="order"><option value="risk">Сначала требуют внимания</option><option value="newest">Сначала новые</option><option value="oldest">Сначала старые</option></select></label>
|
||||||
<button data-action="primary" type="submit">Применить</button>
|
<button data-action="primary" type="submit">Применить</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="notice error" data-admin-error role="alert" hidden></div><div class="notice success" data-admin-status role="status" hidden></div><div data-external-bulk hidden><strong data-external-bulk-count>0 выбрано</strong><textarea data-external-bulk-reason rows="1" maxlength="1000" placeholder="Причина отклонения"></textarea><button data-action="danger" type="button" data-external-bulk-reject>Отклонить выбранные</button></div><div class="moderation-list" data-external-list aria-live="polite"></div>
|
<div class="notice error" data-admin-error role="alert" hidden></div><div class="notice success" data-admin-status role="status" hidden></div><label><input type="checkbox" data-external-select-all> Выбрать все записи на странице</label><div data-external-bulk hidden><strong data-external-bulk-count>0 выбрано</strong><textarea data-external-bulk-reason rows="1" maxlength="1000" placeholder="Причина отклонения"></textarea><button data-action="danger" type="button" data-external-bulk-reject>Отклонить выбранные</button></div><div class="moderation-list" data-external-list aria-live="polite"></div>
|
||||||
<nav data-pages aria-label="Страницы очереди" hidden>
|
<nav data-pages aria-label="Страницы очереди" hidden>
|
||||||
<button data-action="secondary" type="button" data-previous>Предыдущая</button>
|
<button data-action="secondary" type="button" data-previous>Предыдущая</button>
|
||||||
<span data-page-number aria-live="polite"></span>
|
<span data-page-number aria-live="polite"></span>
|
||||||
@@ -41,6 +41,7 @@ const apiUrl = import.meta.env.PUBLIC_API_URL || "http://localhost:8000";
|
|||||||
const logout = document.querySelector<HTMLButtonElement>("[data-admin-logout]");
|
const logout = document.querySelector<HTMLButtonElement>("[data-admin-logout]");
|
||||||
const filters = document.querySelector<HTMLFormElement>(".admin-queue-filters");
|
const filters = document.querySelector<HTMLFormElement>(".admin-queue-filters");
|
||||||
const bulk = document.querySelector<HTMLElement>("[data-external-bulk]");
|
const bulk = document.querySelector<HTMLElement>("[data-external-bulk]");
|
||||||
|
const selectAll = document.querySelector<HTMLInputElement>("[data-external-select-all]");
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
let token = ""; let fishes: Record<string, string>[] = []; let waters: Record<string, string>[] = [];
|
let token = ""; let fishes: Record<string, string>[] = []; let waters: Record<string, string>[] = [];
|
||||||
let sessionTimer: ReturnType<typeof setTimeout> | undefined;
|
let sessionTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
@@ -52,10 +53,11 @@ const apiUrl = import.meta.env.PUBLIC_API_URL || "http://localhost:8000";
|
|||||||
const succeed = (message: string) => { if (error) error.hidden = true; if (status) { status.textContent = message; status.hidden = false; } };
|
const succeed = (message: string) => { if (error) error.hidden = true; if (status) { status.textContent = message; status.hidden = false; } };
|
||||||
const endSession = (message?: string) => { token = ""; if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = undefined; if (login) { login.hidden = false; login.reset(); } if (sessionBar) sessionBar.hidden = true; if (filters) filters.hidden = true; if (list) list.innerHTML = ""; pages?.setAttribute("hidden", ""); if (message) fail(message); };
|
const endSession = (message?: string) => { token = ""; if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = undefined; if (login) { login.hidden = false; login.reset(); } if (sessionBar) sessionBar.hidden = true; if (filters) filters.hidden = true; if (list) list.innerHTML = ""; pages?.setAttribute("hidden", ""); if (message) fail(message); };
|
||||||
const keepSession = () => { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => endSession("Сессия завершена после 15 минут бездействия. Введите токен снова."), 15 * 60 * 1000); };
|
const keepSession = () => { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => endSession("Сессия завершена после 15 минут бездействия. Введите токен снова."), 15 * 60 * 1000); };
|
||||||
const syncBulk = () => { const selected = list?.querySelectorAll<HTMLInputElement>("[data-external-select]:checked").length ?? 0; if (bulk) bulk.hidden = selected === 0; const count = bulk?.querySelector<HTMLElement>("[data-external-bulk-count]"); if (count) count.textContent = `${selected} выбрано`; };
|
const syncBulk = () => { const inputs = [...(list?.querySelectorAll<HTMLInputElement>("[data-external-select]") ?? [])]; const selected = inputs.filter(input => input.checked).length; if (bulk) bulk.hidden = selected === 0; if (selectAll) { selectAll.checked = inputs.length > 0 && selected === inputs.length; selectAll.indeterminate = selected > 0 && selected < inputs.length; } const count = bulk?.querySelector<HTMLElement>("[data-external-bulk-count]"); if (count) count.textContent = `${selected} выбрано`; };
|
||||||
const hydrateBulk = () => { list?.querySelectorAll<HTMLElement>("[data-observation-id]").forEach(card => { if (card.querySelector("[data-external-select]")) return; const label = document.createElement("label"); label.innerHTML = `<input type="checkbox" data-external-select aria-label="Выбрать наблюдение">`; card.prepend(label); }); syncBulk(); };
|
const hydrateBulk = () => { list?.querySelectorAll<HTMLElement>("[data-observation-id]").forEach(card => { if (card.querySelector("[data-external-select]")) return; const label = document.createElement("label"); label.innerHTML = `<input type="checkbox" data-external-select aria-label="Выбрать наблюдение">`; card.prepend(label); }); syncBulk(); };
|
||||||
bulk?.querySelector<HTMLButtonElement>("[data-external-bulk-reject]")?.addEventListener("click", async () => { const cards = [...(list?.querySelectorAll<HTMLInputElement>("[data-external-select]:checked") ?? [])].map(input => input.closest<HTMLElement>("[data-observation-id]")); const reason = bulk.querySelector<HTMLTextAreaElement>("[data-external-bulk-reason]")?.value.trim(); if (!cards.length) return; if (!reason) { fail("Укажите причину отклонения."); return; } try { await json(`${root?.dataset.apiUrl}/api/v1/admin/external-observations/bulk-reject`, {method:"PATCH",headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json"},body:JSON.stringify({reason,items:cards.map(card => ({id:card?.dataset.observationId,expected_version:Number(card?.dataset.version)}))})}); await loadQueue(); succeed(`Отклонено наблюдений: ${cards.length}.`); } catch (cause) { fail(cause instanceof Error ? cause.message : "Ошибка пакетного отклонения."); } });
|
bulk?.querySelector<HTMLButtonElement>("[data-external-bulk-reject]")?.addEventListener("click", async () => { const cards = [...(list?.querySelectorAll<HTMLInputElement>("[data-external-select]:checked") ?? [])].map(input => input.closest<HTMLElement>("[data-observation-id]")); const reason = bulk.querySelector<HTMLTextAreaElement>("[data-external-bulk-reason]")?.value.trim(); if (!cards.length) return; if (!reason) { fail("Укажите причину отклонения."); return; } try { await json(`${root?.dataset.apiUrl}/api/v1/admin/external-observations/bulk-reject`, {method:"PATCH",headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json"},body:JSON.stringify({reason,items:cards.map(card => ({id:card?.dataset.observationId,expected_version:Number(card?.dataset.version)}))})}); await loadQueue(); succeed(`Отклонено наблюдений: ${cards.length}.`); } catch (cause) { fail(cause instanceof Error ? cause.message : "Ошибка пакетного отклонения."); } });
|
||||||
list?.addEventListener("change", event => { if ((event.target as HTMLElement).matches("[data-external-select]")) syncBulk(); });
|
list?.addEventListener("change", event => { if ((event.target as HTMLElement).matches("[data-external-select]")) syncBulk(); });
|
||||||
|
selectAll?.addEventListener("change", () => { list?.querySelectorAll<HTMLInputElement>("[data-external-select]").forEach(input => input.checked = selectAll.checked); syncBulk(); });
|
||||||
const options = (items: Record<string, string>[], selected?: unknown) => items.map(item => `<option value="${esc(item.slug)}" ${item.slug === selected ? "selected" : ""}>${esc(item.name_ru)}</option>`).join("");
|
const options = (items: Record<string, string>[], selected?: unknown) => items.map(item => `<option value="${esc(item.slug)}" ${item.slug === selected ? "selected" : ""}>${esc(item.name_ru)}</option>`).join("");
|
||||||
async function json(url: string, init: RequestInit = {}) { const response = await fetch(url, init); if (response.status === 409) { await loadQueue(); throw new Error("Запись уже изменена в другой вкладке. Очередь обновлена."); } if (!response.ok) { if (adminEndsSession(response.status)) endSession(); throw new Error(adminErrorMessage(response.status, `Запрос завершился ошибкой ${response.status}.`)); } if ((init.headers as Record<string, string> | undefined)?.Authorization) keepSession(); return response.json(); }
|
async function json(url: string, init: RequestInit = {}) { const response = await fetch(url, init); if (response.status === 409) { await loadQueue(); throw new Error("Запись уже изменена в другой вкладке. Очередь обновлена."); } if (!response.ok) { if (adminEndsSession(response.status)) endSession(); throw new Error(adminErrorMessage(response.status, `Запрос завершился ошибкой ${response.status}.`)); } if ((init.headers as Record<string, string> | undefined)?.Authorization) keepSession(); return response.json(); }
|
||||||
async function loadQueue() {
|
async function loadQueue() {
|
||||||
|
|||||||
Reference in New Issue
Block a user