feat: add bulk publish controls
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><label><input type="checkbox" data-external-select-all> Выбрать все записи на странице</label><div data-external-bulk hidden><strong data-external-bulk-count>0 выбрано</strong><label>Рыба<select data-external-bulk-fish><option value="">Выберите…</option></select></label><label>Водоём<select data-external-bulk-waterbody><option value="">Выберите…</option></select></label><textarea data-external-bulk-reason rows="1" maxlength="1000" placeholder="Примечание или причина отклонения"></textarea><button data-action="secondary" type="button" data-external-bulk-map>Сопоставить выбранные</button><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><label>Рыба<select data-external-bulk-fish><option value="">Выберите…</option></select></label><label>Водоём<select data-external-bulk-waterbody><option value="">Выберите…</option></select></label><textarea data-external-bulk-reason rows="1" maxlength="1000" placeholder="Примечание или причина отклонения"></textarea><button data-action="secondary" type="button" data-external-bulk-map>Сопоставить выбранные</button><button data-action="primary" type="button" data-external-bulk-publish>Опубликовать выбранные</button><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>
|
||||||
@@ -57,6 +57,7 @@ const apiUrl = import.meta.env.PUBLIC_API_URL || "http://localhost:8000";
|
|||||||
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); }); const fishSelect = bulk?.querySelector<HTMLSelectElement>("[data-external-bulk-fish]"); const waterSelect = bulk?.querySelector<HTMLSelectElement>("[data-external-bulk-waterbody]"); if (fishSelect && fishSelect.options.length === 1 && fishes.length) fishSelect.innerHTML = '<option value="">Выберите…</option>' + options(fishes); if (waterSelect && waterSelect.options.length === 1 && waters.length) waterSelect.innerHTML = '<option value="">Выберите…</option>' + options(waters); 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); }); const fishSelect = bulk?.querySelector<HTMLSelectElement>("[data-external-bulk-fish]"); const waterSelect = bulk?.querySelector<HTMLSelectElement>("[data-external-bulk-waterbody]"); if (fishSelect && fishSelect.options.length === 1 && fishes.length) fishSelect.innerHTML = '<option value="">Выберите…</option>' + options(fishes); if (waterSelect && waterSelect.options.length === 1 && waters.length) waterSelect.innerHTML = '<option value="">Выберите…</option>' + options(waters); syncBulk(); };
|
||||||
const selectedCards = () => [...(list?.querySelectorAll<HTMLInputElement>("[data-external-select]:checked") ?? [])].map(input => input.closest<HTMLElement>("[data-observation-id]")).filter((card): card is HTMLElement => Boolean(card));
|
const selectedCards = () => [...(list?.querySelectorAll<HTMLInputElement>("[data-external-select]:checked") ?? [])].map(input => input.closest<HTMLElement>("[data-observation-id]")).filter((card): card is HTMLElement => Boolean(card));
|
||||||
bulk?.querySelector<HTMLButtonElement>("[data-external-bulk-map]")?.addEventListener("click", async () => { const cards = selectedCards(); const fish_slug = bulk.querySelector<HTMLSelectElement>("[data-external-bulk-fish]")?.value; const waterbody_slug = bulk.querySelector<HTMLSelectElement>("[data-external-bulk-waterbody]")?.value; if (!cards.length) return; if (!fish_slug || !waterbody_slug) { fail("Выберите каноническую рыбу и водоём."); return; } try { await json(`${root?.dataset.apiUrl}/api/v1/admin/external-observations/bulk-mapping`, {method:"PATCH",headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json"},body:JSON.stringify({fish_slug,waterbody_slug,note:bulk.querySelector<HTMLTextAreaElement>("[data-external-bulk-reason]")?.value.trim() || null,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-map]")?.addEventListener("click", async () => { const cards = selectedCards(); const fish_slug = bulk.querySelector<HTMLSelectElement>("[data-external-bulk-fish]")?.value; const waterbody_slug = bulk.querySelector<HTMLSelectElement>("[data-external-bulk-waterbody]")?.value; if (!cards.length) return; if (!fish_slug || !waterbody_slug) { fail("Выберите каноническую рыбу и водоём."); return; } try { await json(`${root?.dataset.apiUrl}/api/v1/admin/external-observations/bulk-mapping`, {method:"PATCH",headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json"},body:JSON.stringify({fish_slug,waterbody_slug,note:bulk.querySelector<HTMLTextAreaElement>("[data-external-bulk-reason]")?.value.trim() || null,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-publish]")?.addEventListener("click", async () => { const cards = selectedCards(); if (!cards.length) return; try { await json(`${root?.dataset.apiUrl}/api/v1/admin/external-observations/bulk-publish`, {method:"POST",headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json"},body:JSON.stringify({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 = selectedCards(); 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 = selectedCards(); 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(); });
|
selectAll?.addEventListener("change", () => { list?.querySelectorAll<HTMLInputElement>("[data-external-select]").forEach(input => input.checked = selectAll.checked); syncBulk(); });
|
||||||
|
|||||||
Reference in New Issue
Block a user