From 788f2fec2ce6f7abb851be3e4ace15b2ccc32548 Mon Sep 17 00:00:00 2001 From: jerick Date: Fri, 28 Nov 2025 12:28:22 -0500 Subject: [PATCH] Changed populate to also update status of members --- static/dashboard.js | 110 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 22 deletions(-) diff --git a/static/dashboard.js b/static/dashboard.js index 68bb8d8..322ad32 100644 --- a/static/dashboard.js +++ b/static/dashboard.js @@ -13,6 +13,27 @@ const enemyContainer = document.getElementById("enemy-container"); // utility to safe parse ints function toInt(v) { const n = Number(v); return Number.isNaN(n) ? null : n; } +// ---- Apply status CSS class to the .status-text inside a member card ---- +function applyStatusClass(member) { + const span = member.domElement?.querySelector(".status-text"); + if (!span) return; + span.classList.remove("status-ok", "status-traveling", "status-hospitalized"); + const s = String(member.status || "").toLowerCase(); + if (s === "okay") span.classList.add("status-ok"); + else if (s === "traveling" || s === "abroad") span.classList.add("status-traveling"); + else if (s === "hospitalized" || s === "hospital") span.classList.add("status-hospitalized"); +} + +// ---- Decide which CSS class a status should use (helper used when creating card) --- +function statusClass(status) { + if (!status) return ""; + status = status.toLowerCase(); + if (status.includes("okay")) return "status-ok"; + if (status.includes("travel") || status.includes("abroad")) return "status-traveling"; + if (status.includes("hospital")) return "status-hospitalized"; + return ""; +} + // ---- Create structured card ---- function createMemberCard(member, kind) { const card = document.createElement("div"); @@ -29,13 +50,15 @@ function createMemberCard(member, kind) { const statsDiv = document.createElement("div"); statsDiv.className = "stats"; - statsDiv.innerHTML = `Lv: ${member.level}
Est: ${member.estimate}
Status: ${member.status || "Unknown"}`; + // Ensure initial status text and class (maybe "Unknown" until refresh) + statsDiv.innerHTML = `Lv: ${member.level}
Est: ${member.estimate}
Status: ${member.status || "Unknown"}`; card.appendChild(nameDiv); card.appendChild(statsDiv); member.domElement = card; + // drag payload is JSON (kind + id) card.addEventListener("dragstart", (e) => { const payload = JSON.stringify({ kind, id: member.id }); e.dataTransfer.setData("text/plain", payload); @@ -45,28 +68,21 @@ function createMemberCard(member, kind) { card.style.opacity = "1"; }); + // Apply initial status class (for cases where member.status was set by populate) applyStatusClass(member); return card; } -// ---- Apply status CSS class to the .status-text inside a member card ---- -function applyStatusClass(member) { - const span = member.domElement?.querySelector(".status-text"); - if (!span) return; - span.classList.remove("status-ok", "status-traveling", "status-hospitalized"); - const s = String(member.status || "").toLowerCase(); - if (s === "okay") span.classList.add("status-ok"); - else if (s === "traveling" || s === "abroad") span.classList.add("status-traveling"); - else if (s === "hospitalized" || s === "hospital") span.classList.add("status-hospitalized"); -} - // ---- Load static members from backend ---- async function loadMembers(faction) { const url = faction === "friendly" ? "/api/friendly_members" : "/api/enemy_members"; try { const res = await fetch(url, { cache: "no-store" }); - if (!res.ok) return console.error("Failed to fetch members:", res.status); + if (!res.ok) { + console.error("Failed to fetch members:", res.status); + return; + } const arr = await res.json(); const container = faction === "friendly" ? friendlyContainer : enemyContainer; const map = faction === "friendly" ? friendlyMembers : enemyMembers; @@ -80,6 +96,9 @@ async function loadMembers(faction) { map.set(m.id, m); container.appendChild(card); }); + + // after we've added elements, ensure drop listeners are attached + setupDropZones(); } catch (err) { console.error("loadMembers error", err); } @@ -99,8 +118,10 @@ async function refreshStatus(faction) { if (!member) return; member.status = statusData[k].status; const span = member.domElement.querySelector(".status-text"); - span.textContent = member.status; - applyStatusClass(member); + if (span) { + span.textContent = member.status; + applyStatusClass(member); + } }); } catch (err) { console.error("refreshStatus error", err); @@ -108,6 +129,7 @@ async function refreshStatus(faction) { } // ---- Populate endpoints ---- +// NOW: populate also immediately fetches the latest status snapshot and applies it. async function populateFriendly() { const id = toInt(document.getElementById("friendly-id").value); if (!id) return alert("Enter friendly faction ID"); @@ -116,7 +138,11 @@ async function populateFriendly() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ faction_id: id, interval: 0 }) }); + + // Rebuild list and then immediately fetch status snapshot await loadMembers("friendly"); + // try to pull status once so the status text shows up immediately + await refreshStatus("friendly"); } async function populateEnemy() { @@ -127,7 +153,9 @@ async function populateEnemy() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ faction_id: id, interval: 0 }) }); + await loadMembers("enemy"); + await refreshStatus("enemy"); } // ---- Start/Stop status refresh loops ---- @@ -136,6 +164,11 @@ let enemyStatusIntervalHandle = null; async function toggleFriendlyStatus() { const btn = document.getElementById("friendly-status-btn"); + if (!btn) { + console.error("friendly-status-btn not found"); + return; + } + if (friendlyStatusIntervalHandle) { clearInterval(friendlyStatusIntervalHandle); friendlyStatusIntervalHandle = null; @@ -155,11 +188,17 @@ async function toggleFriendlyStatus() { friendlyStatusIntervalHandle = setInterval(() => refreshStatus("friendly"), interval * 1000); btn.textContent = "Stop Refresh"; + // do an immediate status fetch as well refreshStatus("friendly"); } async function toggleEnemyStatus() { const btn = document.getElementById("enemy-status-btn"); + if (!btn) { + console.error("enemy-status-btn not found"); + return; + } + if (enemyStatusIntervalHandle) { clearInterval(enemyStatusIntervalHandle); enemyStatusIntervalHandle = null; @@ -182,14 +221,25 @@ async function toggleEnemyStatus() { refreshStatus("enemy"); } -// ---- Drag & drop handling for drop-zones ---- +// ------------------- +// DRAG & DROP SYSTEM +// ------------------- function setupDropZones() { const dropZones = document.querySelectorAll(".drop-zone, .member-list"); dropZones.forEach(zone => { + // attach listeners only once + if (zone.dataset._drag_listeners_attached) return; + zone.addEventListener("dragover", (e) => { e.preventDefault(); - const raw = e.dataTransfer.types.includes("text/plain") ? e.dataTransfer.getData("text/plain") : null; + // check payload validity + let raw = null; + try { + raw = e.dataTransfer.getData("text/plain"); + } catch (err) { + raw = null; + } let valid = false; if (raw) { try { @@ -211,7 +261,13 @@ function setupDropZones() { zone.addEventListener("drop", (e) => { e.preventDefault(); zone.classList.remove("dragover-valid", "dragover-invalid"); - const raw = e.dataTransfer.getData("text/plain"); + + let raw = null; + try { + raw = e.dataTransfer.getData("text/plain"); + } catch (err) { + raw = null; + } if (!raw) return; let payload; try { payload = JSON.parse(raw); } catch { return; } @@ -234,10 +290,14 @@ function setupDropZones() { // persist assignment in localStorage saveAssignments(); }); + + zone.dataset._drag_listeners_attached = "1"; }); } -// ---- Persist card assignments to localStorage ---- +// ------------------- +// localStorage helpers for battle groups (the format used earlier) +// ------------------- function saveAssignments() { const groups = document.querySelectorAll(".group"); const data = {}; @@ -250,8 +310,7 @@ function saveAssignments() { localStorage.setItem("battleAssignments", JSON.stringify(data)); } -// ---- Restore card assignments from localStorage ---- -function loadAssignments() { +function loadAssignmentsFromStorage() { const data = JSON.parse(localStorage.getItem("battleAssignments") || "{}"); Object.keys(data).forEach(gid => { const group = document.querySelector(`.group[data-id="${gid}"]`); @@ -286,6 +345,13 @@ function wireUp() { document.getElementById("enemy-populate-btn").addEventListener("click", populateEnemy); document.getElementById("friendly-status-btn").addEventListener("click", toggleFriendlyStatus); document.getElementById("enemy-status-btn").addEventListener("click", toggleEnemyStatus); + document.getElementById("reset-groups-btn").addEventListener("click", () => { + // clear storage and put members back + localStorage.removeItem("battleAssignments"); + // move all back to lists + friendlyMembers.forEach(m => { if (m.domElement) friendlyContainer.appendChild(m.domElement); }); + enemyMembers.forEach(m => { if (m.domElement) enemyContainer.appendChild(m.domElement); }); + }); setupDropZones(); attachCardClickLogging(); @@ -296,5 +362,5 @@ document.addEventListener("DOMContentLoaded", async () => { wireUp(); await loadMembers("friendly"); await loadMembers("enemy"); - loadAssignments(); // restore positions after load + loadAssignmentsFromStorage(); // restore positions after load });