window.cadTasks = { contracts: [], requests: [], groups: [], activity: [], session: {}, mode: "operations", dispatchView: "board", activeTab: "contracts", selectedDispatchGroupId: "", selectedDispatchTaskId: "", selectedDispatchRequestId: "", focusStatusTimer: null, requestModalType: "", statuses: [ "available", "en_route", "on_task", "holding", "danger", "unavailable", ], roles: ["infantry", "recon", "armor", "air", "logistics", "support"], requestTypes: [ { id: "medevac_9line", label: "9-Line MEDEVAC", defaultPriority: "emergency", fields: [ { id: "pickup_location", label: "Line 1 Pickup Location", type: "text", defaultFromGroupPosition: true, }, { id: "radio_freq", label: "Line 2 Radio / Call Sign", type: "text", }, { id: "precedence", label: "Line 3 Precedence", type: "select", options: [ "urgent", "urgent_surgical", "priority", "routine", "convenience", ], }, { id: "special_equipment", label: "Line 4 Special Equipment", type: "select", options: ["none", "hoist", "extraction", "ventilator"], }, { id: "patient_type", label: "Line 5 Patient Type", type: "select", options: ["litter", "ambulatory", "mixed"], }, { id: "security", label: "Line 6 Security", type: "select", options: [ "secure", "possible_enemy", "enemy_in_area", "hot", ], }, { id: "marking", label: "Line 7 Marking", type: "select", options: ["panels", "smoke", "ir", "none", "other"], }, { id: "patient_nationality", label: "Line 8 Patient Nationality", type: "select", options: ["coalition", "civilian", "enemy", "epw", "mixed"], }, { id: "terrain", label: "Line 9 Terrain", type: "select", options: [ "flat", "restricted", "slope", "rooftop", "wooded", ], }, ], }, { id: "ace_lace", label: "ACE/LACE", defaultPriority: "routine", fields: [ { id: "ammo", label: "Ammo", type: "textarea" }, { id: "casualties", label: "Casualties", type: "textarea" }, { id: "equipment", label: "Equipment", type: "textarea" }, { id: "notes", label: "Notes", type: "textarea" }, ], }, { id: "fire_support", label: "Fire Support", defaultPriority: "priority", fields: [ { id: "target_location", label: "Target Location", type: "text", defaultFromGroupPosition: true, }, { id: "target_description", label: "Target Description", type: "textarea", }, { id: "requested_effect", label: "Requested Effect", type: "select", options: [ "suppress", "destroy", "illum", "smoke", "screen", ], }, { id: "ordnance", label: "Requested Ordnance", type: "text" }, { id: "danger_close", label: "Danger Close", type: "select", options: ["no", "yes"], }, { id: "remarks", label: "Remarks", type: "textarea" }, ], }, { id: "air_support", label: "Air Support", defaultPriority: "priority", fields: [ { id: "target_location", label: "Target Location", type: "text", defaultFromGroupPosition: true, }, { id: "target_description", label: "Target Description", type: "textarea", }, { id: "target_marking", label: "Target Marking", type: "select", options: ["smoke", "ir", "laser", "grid", "visual"], }, { id: "requested_effect", label: "Requested Effect", type: "select", options: [ "show_of_force", "escort", "suppress", "destroy", "recon", ], }, { id: "remarks", label: "Remarks", type: "textarea" }, ], }, { id: "logreq", label: "LOGREQ", defaultPriority: "priority", fields: [ { id: "category", label: "Category", type: "select", options: [ "ammo", "medical", "fuel", "repair", "vehicle", "equipment", "weapons", "mixed", ], }, { id: "delivery_method", label: "Delivery Method", type: "select", options: [ "ground", "airdrop", "pickup", "dispatch_discretion", ], }, { id: "delivery_location", label: "Delivery Location", type: "text", defaultFromGroupPosition: true, }, { id: "requested_items", label: "Requested Items", type: "textarea", }, { id: "quantity", label: "Quantity / Package", type: "text", }, { id: "remarks", label: "Remarks", type: "textarea", }, ], }, ], init() { document.querySelectorAll(".cad-tab").forEach((tab) => { tab.addEventListener("click", () => { this.setActiveTab(tab.dataset.tab || "contracts"); }); }); document .getElementById("cadRequestModalCloseBtn") .addEventListener("click", () => { this.closeRequestModal(); }); document .getElementById("cadRequestModalSaveBtn") .addEventListener("click", () => { this.submitSupportRequest(); }); document .querySelector("#cadRequestModal .cad-modal-backdrop") .addEventListener("click", () => { this.closeRequestModal(); }); window.ForgeBridge.on("cad::hydrate", (payload) => { this.setHydratePayload(payload || {}); }); window.ForgeBridge.on("cad::assignment::response", (payload) => { this.handleServerResponse(!!payload.success, payload.message || ""); }); window.ForgeBridge.on("cad::group::response", (payload) => { this.handleServerResponse(!!payload.success, payload.message || ""); }); window.ForgeBridge.on("cad::request::response", (payload) => { this.handleServerResponse(!!payload.success, payload.message || ""); }); window.ForgeBridge.ready({ loaded: true }); }, setActiveTab(tabName) { this.activeTab = tabName || "contracts"; document.querySelectorAll(".cad-tab").forEach((tab) => { tab.classList.toggle( "is-active", tab.dataset.tab === this.activeTab, ); }); document.querySelectorAll("[data-panel]").forEach((panel) => { panel.classList.toggle( "is-active", panel.dataset.panel === this.activeTab, ); }); }, syncLayoutState() { const tabsEl = document.querySelector(".cad-tabs"); const contractsTab = document.getElementById("tabContractsBtn"); const rosterTab = document.getElementById("tabRosterBtn"); const requestsTab = document.getElementById("tabRequestsBtn"); const activityTab = document.getElementById("tabActivityBtn"); const contractsPanel = document.getElementById("contractsPanel"); const rosterPanel = document.getElementById("rosterPanel"); const requestsPanel = document.getElementById("requestsPanel"); const activityPanel = document.getElementById("activityPanel"); const contractsHeader = contractsPanel?.querySelector( ".cad-section-header", ); const rosterHeader = rosterPanel?.querySelector(".cad-section-header"); if (this.isDispatchMapMode()) { if (tabsEl) { tabsEl.style.display = ""; tabsEl.classList.remove("is-two-col"); tabsEl.classList.add("is-three-col"); } if (contractsTab) { contractsTab.style.display = ""; } if (rosterTab) { rosterTab.textContent = "Groups"; } if (activityTab) { activityTab.style.display = "none"; } if (requestsTab) { requestsTab.style.display = ""; } if (activityPanel) { activityPanel.classList.remove("is-active"); activityPanel.style.display = "none"; } if (requestsPanel) { requestsPanel.style.display = ""; } if (rosterPanel) { rosterPanel.style.display = ""; } if (rosterHeader) { rosterHeader.textContent = "Active Groups"; } if (contractsPanel) { contractsPanel.style.display = ""; } if (contractsHeader) { contractsHeader.textContent = "Contracts"; } if (!["contracts", "roster", "requests"].includes(this.activeTab)) { this.activeTab = "contracts"; } return; } if (tabsEl) { tabsEl.style.display = ""; tabsEl.classList.remove("is-three-col"); tabsEl.classList.remove("is-two-col"); } if (contractsTab) { contractsTab.style.display = ""; } if (rosterTab) { rosterTab.textContent = "Roster"; } if (activityTab) { activityTab.style.display = ""; } if (requestsTab) { requestsTab.style.display = ""; } if (contractsPanel) { contractsPanel.style.display = ""; } if (activityPanel) { activityPanel.style.display = ""; } if (requestsPanel) { requestsPanel.style.display = ""; } if (rosterPanel) { rosterPanel.style.display = ""; } if (rosterHeader) { rosterHeader.textContent = "Roster"; } if (contractsHeader) { contractsHeader.textContent = "Contracts"; } }, setHydratePayload(payload) { this.contracts = Array.isArray(payload.contracts) ? payload.contracts : []; this.requests = Array.isArray(payload.requests) ? payload.requests : []; this.groups = Array.isArray(payload.groups) ? payload.groups : []; this.activity = Array.isArray(payload.activity) ? payload.activity : []; this.session = payload.session && typeof payload.session === "object" ? payload.session : {}; this.mode = payload && typeof payload.mode === "string" ? payload.mode : "operations"; this.dispatchView = payload && typeof payload.dispatchView === "string" ? payload.dispatchView : "board"; const statusEl = document.getElementById("cadStatusMessage"); if ( statusEl && (!statusEl.dataset.type || statusEl.dataset.type === "info") ) { this.setStatus("", ""); } if ( this.selectedDispatchGroupId && !this.groups.some( (group) => group.groupId === this.selectedDispatchGroupId, ) ) { this.selectedDispatchGroupId = ""; } if ( this.selectedDispatchTaskId && !this.contracts.some((task) => { const taskId = task.taskId || task.taskID || ""; return taskId === this.selectedDispatchTaskId; }) ) { this.selectedDispatchTaskId = ""; } if ( this.selectedDispatchRequestId && !this.requests.some( (request) => (request.requestId || "") === this.selectedDispatchRequestId, ) ) { this.selectedDispatchRequestId = ""; } if ( this.mode === "dispatch" && this.dispatchView === "map" && !["contracts", "roster", "requests"].includes(this.activeTab) ) { this.activeTab = "contracts"; } this.render(); }, setStatus(message, type) { const statusEl = document.getElementById("cadStatusMessage"); if (!statusEl) { return; } statusEl.textContent = message || ""; statusEl.dataset.type = type || ""; }, getDangerGroups() { return this.groups.filter((group) => (group.status || "") === "danger"); }, getSupportAlertRequests() { return this.requests.filter((request) => ["medevac_9line", "fire_support", "air_support"].includes( request.type || "", ), ); }, buildSupportAlertMessage() { const alertRequests = this.getSupportAlertRequests(); if (!alertRequests.length) { return ""; } const labels = alertRequests.map((request) => { const groupLabel = request.groupCallsign || request.groupId || "Unknown Group"; const typeLabel = this.getRequestTypeLabel( request.type || "request", ); return `${groupLabel} ${typeLabel}`; }); return `Support request alert: ${labels.join(", ")}`; }, getCurrentGroupCoordinates() { const currentGroup = this.getCurrentGroup(); const position = Array.isArray(currentGroup?.position) ? currentGroup.position : [0, 0, 0]; return window.mapUI.formatPosition(position); }, getSortedGroups() { return this.groups.slice().sort((left, right) => { const leftDanger = (left.status || "") === "danger" ? 0 : 1; const rightDanger = (right.status || "") === "danger" ? 0 : 1; if (leftDanger !== rightDanger) { return leftDanger - rightDanger; } const leftCallsign = left.callsign || left.groupId || ""; const rightCallsign = right.callsign || right.groupId || ""; return leftCallsign.localeCompare(rightCallsign); }); }, isDispatchOrder(entry) { return ( !!entry.isDispatchOrder || (entry.type || "") === "dispatch_order" ); }, formatTypeLabel(entry) { const typeLabel = (entry.type || "task").replaceAll("_", " "); return this.isDispatchOrder(entry) ? "dispatch order" : typeLabel; }, getRequestDefinition(typeID) { return this.requestTypes.find((entry) => entry.id === typeID) || null; }, getRequestTypeLabel(typeID) { return this.getRequestDefinition(typeID)?.label || typeID; }, canSubmitSupportRequest() { return this.mode === "operations" && this.isLeader(); }, openRequestModal(typeID) { const definition = this.getRequestDefinition(typeID); if (!definition) { return; } this.requestModalType = typeID; document.getElementById("cadRequestModalTitle").textContent = definition.label; document.getElementById("cadRequestPrioritySelect").value = definition.defaultPriority || "priority"; this.renderRequestFields(definition); document .getElementById("cadRequestModal") .classList.remove("is-hidden"); }, closeRequestModal() { this.requestModalType = ""; document.getElementById("cadRequestFields").innerHTML = ""; document.getElementById("cadRequestModal").classList.add("is-hidden"); }, renderRequestFields(definition) { const container = document.getElementById("cadRequestFields"); if (!container || !definition) { return; } const coords = this.getCurrentGroupCoordinates(); container.innerHTML = definition.fields .map((field) => { const defaultValue = field.defaultFromGroupPosition ? coords : ""; if (field.type === "select") { return ` `; } if (field.type === "textarea") { return ` `; } return ` `; }) .join(""); }, submitSupportRequest() { const definition = this.getRequestDefinition(this.requestModalType); if (!definition) { return; } const fields = {}; definition.fields.forEach((field) => { const input = document.getElementById( `cadRequestField_${field.id}`, ); fields[field.id] = input ? String(input.value || "").trim() : ""; }); const priority = document.getElementById( "cadRequestPrioritySelect", ).value; this.setStatus("Submitting support request...", "info"); window.mapUI.sendEvent("cad::supportRequest::submit", { type: definition.id, fields: fields, priority: priority, }); this.closeRequestModal(); }, closeSupportRequest(requestID) { if (!requestID) { return; } this.setStatus( this.isDispatchMode() ? "Closing support request..." : "Cancelling support request...", "info", ); window.mapUI.sendEvent("cad::supportRequest::close", { requestID: requestID, }); }, renderRequests() { const listEl = document.getElementById("requestList"); if (!listEl) { return; } if (this.isDispatchMapMode()) { const dispatchRequests = this.requests .slice() .sort((left, right) => { const leftTitle = left.title || left.requestId || ""; const rightTitle = right.title || right.requestId || ""; return leftTitle.localeCompare(rightTitle); }); if (!dispatchRequests.length) { listEl.innerHTML = '

No support requests are currently active.

'; return; } listEl.innerHTML = dispatchRequests .map((request) => { const requestID = request.requestId || ""; const position = Array.isArray(request.position) ? request.position : [0, 0, 0]; const isSelected = requestID === this.selectedDispatchRequestId; const isWarning = [ "medevac_9line", "fire_support", "air_support", ].includes(request.type || ""); return ` `; }) .join(""); return; } const requestButtons = this.canSubmitSupportRequest() ? `
${this.requestTypes .map( (requestType) => ` `, ) .join("")}
` : ""; if (!this.requests.length) { listEl.innerHTML = ` ${requestButtons}

No support requests are currently active.

`; return; } listEl.innerHTML = ` ${requestButtons} ${this.requests .map((request) => { const isOwnGroupLeader = this.isLeader() && (request.groupId || "") === this.getPlayerGroupId(); const canClose = this.canDispatch() || isOwnGroupLeader; const requestActionLabel = this.isDispatchMode() ? "Close" : "Cancel"; return `
${request.title || this.getRequestTypeLabel(request.type || "")} ${(request.priority || "priority").replaceAll("_", " ")}

${request.summary || ""}

Group: ${request.groupCallsign || request.groupId || "Unknown"} ${this.getRequestTypeLabel(request.type || "")}
${ canClose ? `
` : "" }
`; }) .join("")} `; }, updateDangerAlert() { const alertEl = document.getElementById("cadDangerAlert"); if (!alertEl) { return; } if (!this.isDispatchMapMode()) { alertEl.textContent = ""; alertEl.classList.add("is-hidden"); return; } const dangerGroups = this.getDangerGroups(); if (!dangerGroups.length) { alertEl.textContent = ""; alertEl.classList.add("is-hidden"); return; } const callsigns = dangerGroups.map( (group) => group.callsign || group.groupId || "Unknown Group", ); alertEl.textContent = `Danger alert active: ${callsigns.join(", ")}`; alertEl.classList.remove("is-hidden"); }, updateRequestAlert() { const alertEl = document.getElementById("cadRequestAlert"); if (!alertEl) { return; } if (!this.isDispatchMapMode()) { alertEl.textContent = ""; alertEl.classList.add("is-hidden"); return; } const alertMessage = this.buildSupportAlertMessage(); if (!alertMessage) { alertEl.textContent = ""; alertEl.classList.add("is-hidden"); return; } alertEl.textContent = alertMessage; alertEl.classList.remove("is-hidden"); }, clearFocusStatusSoon(message) { if (this.focusStatusTimer) { window.clearTimeout(this.focusStatusTimer); } this.focusStatusTimer = window.setTimeout(() => { const statusEl = document.getElementById("cadStatusMessage"); if (!statusEl) { return; } if ( statusEl.dataset.type === "info" && statusEl.textContent === message ) { this.setStatus("", ""); } }, 1800); }, handleServerResponse(success, message) { this.setStatus( message || (success ? "CAD update succeeded." : "CAD update failed."), success ? "success" : "error", ); }, acknowledgeTask(taskID) { this.setStatus("Acknowledging contract...", "info"); window.mapUI.sendEvent("cad::tasks::acknowledge", { taskID: taskID }); }, declineTask(taskID) { this.setStatus("Declining contract...", "info"); window.mapUI.sendEvent("cad::tasks::decline", { taskID: taskID }); }, updateGroupStatus(groupID, status) { this.setStatus("Updating group status...", "info"); window.mapUI.sendEvent("cad::groups::status", { groupID: groupID, status: status, }); }, updateGroupRole(groupID, role) { this.setStatus("Updating group role...", "info"); window.mapUI.sendEvent("cad::groups::role", { groupID: groupID, role: role, }); }, focusGroup(groupID) { const group = this.groups.find((entry) => entry.groupId === groupID); if (!group) { this.setStatus("Selected group is no longer available.", "error"); return; } this.selectedDispatchGroupId = groupID; this.selectedDispatchTaskId = ""; this.selectedDispatchRequestId = ""; const statusMessage = `Centering map on ${group.callsign || group.groupId || "group"}...`; this.setStatus(statusMessage, "info"); this.clearFocusStatusSoon(statusMessage); window.mapUI.sendEvent("cad::groups::focus", { groupID: groupID, }); this.render(); }, focusTask(taskID) { const task = this.contracts.find((entry) => { const entryTaskID = entry.taskId || entry.taskID || ""; return entryTaskID === taskID; }); if (!task) { this.setStatus( "Selected contract is no longer available.", "error", ); return; } this.selectedDispatchTaskId = taskID; this.selectedDispatchGroupId = ""; this.selectedDispatchRequestId = ""; const statusMessage = `Centering map on ${task.title || taskID}...`; this.setStatus(statusMessage, "info"); this.clearFocusStatusSoon(statusMessage); window.mapUI.sendEvent("cad::tasks::focus", { taskID: taskID, }); this.render(); }, focusRequest(requestID) { const request = this.requests.find( (entry) => (entry.requestId || "") === requestID, ); if (!request) { this.setStatus("Selected request is no longer available.", "error"); return; } const position = Array.isArray(request.position) ? request.position : []; if (position.length < 2) { this.setStatus("Selected request has no map position.", "error"); return; } this.selectedDispatchRequestId = requestID; this.selectedDispatchGroupId = ""; this.selectedDispatchTaskId = ""; const statusMessage = `Centering map on ${request.title || requestID}...`; this.setStatus(statusMessage, "info"); this.clearFocusStatusSoon(statusMessage); window.mapUI.sendEvent("cad::requests::focus", { requestID: requestID, }); this.render(); }, getPlayerGroupId() { return this.session.groupId || ""; }, getCurrentGroup() { const currentGroupId = this.getPlayerGroupId(); return ( this.groups.find((group) => group.groupId === currentGroupId) || null ); }, normalizeCollection(value) { if (Array.isArray(value)) { return value; } if (value && typeof value === "object") { return Object.values(value); } return []; }, canDispatch() { return !!this.session.isDispatcher; }, isDispatchMode() { return this.mode === "dispatch"; }, isDispatchMapMode() { return this.mode === "dispatch" && this.dispatchView === "map"; }, isLeader() { return !!this.session.isLeader; }, renderContracts() { const listEl = document.getElementById("taskList"); if (!listEl) { return; } if (this.isDispatchMapMode()) { if (!this.contracts.length) { listEl.innerHTML = '

No contracts are currently available.

'; return; } const dispatchContracts = this.contracts .slice() .sort((left, right) => { const leftAssigned = (left.assignmentState || "unassigned") === "unassigned" ? 0 : 1; const rightAssigned = (right.assignmentState || "unassigned") === "unassigned" ? 0 : 1; if (leftAssigned !== rightAssigned) { return leftAssigned - rightAssigned; } const leftId = left.taskId || left.taskID || ""; const rightId = right.taskId || right.taskID || ""; return leftId.localeCompare(rightId); }); listEl.innerHTML = dispatchContracts .map((task) => { const taskId = task.taskId || task.taskID || ""; const position = Array.isArray(task.position) ? task.position : [0, 0, 0]; const assignedGroupId = task.assignedGroupId || ""; const assignmentState = task.assignmentState || "unassigned"; const assignedGroup = this.groups.find( (group) => group.groupId === assignedGroupId, ); const isSelected = taskId === this.selectedDispatchTaskId; const stateLabel = assignmentState === "unassigned" ? "Unassigned" : `${assignmentState}: ${assignedGroup ? assignedGroup.callsign : assignedGroupId || "Unknown"}`; return ` `; }) .join(""); return; } const currentGroupId = this.getPlayerGroupId(); const visibleContracts = this.contracts.filter( (task) => (task.assignedGroupId || "") === currentGroupId, ); if (!visibleContracts.length) { listEl.innerHTML = '

No contract is currently assigned to your group.

'; return; } listEl.innerHTML = visibleContracts .map((task) => { const taskId = task.taskId || task.taskID || ""; const position = Array.isArray(task.position) ? task.position : [0, 0, 0]; const assignedGroupId = task.assignedGroupId || ""; const assignmentState = task.assignmentState || "unassigned"; const assignedGroup = this.groups.find( (group) => group.groupId === assignedGroupId, ); const isAssignedToLeader = this.isLeader() && assignedGroupId === currentGroupId; return `
${task.title || taskId} ${this.formatTypeLabel(task)}

${task.description || ""}

${assignmentState === "unassigned" ? "Available" : `${assignmentState}: ${assignedGroup ? assignedGroup.callsign : assignedGroupId}`} ${window.mapUI.formatPosition(position)}
${ isAssignedToLeader && assignmentState === "assigned" ? `
` : "" }
`; }) .join(""); }, renderRoster() { const listEl = document.getElementById("rosterList"); if (!listEl) { return; } if (this.isDispatchMapMode()) { if (!this.groups.length) { listEl.innerHTML = '

No active groups are currently available.

'; return; } listEl.innerHTML = this.getSortedGroups() .map((group) => { const isSelected = (group.groupId || "") === this.selectedDispatchGroupId; const isDanger = (group.status || "") === "danger"; return ` `; }) .join(""); return; } const currentGroup = this.getCurrentGroup(); if (!currentGroup) { listEl.innerHTML = '

Your group is not currently available.

'; return; } const roster = this.normalizeCollection(currentGroup.members); const isDanger = (currentGroup.status || "") === "danger"; if (!roster.length) { listEl.innerHTML = '

No roster members are currently available.

'; return; } listEl.innerHTML = `
${currentGroup.callsign || currentGroup.groupId || "Current Group"} ${roster.length} member${roster.length === 1 ? "" : "s"} ${isDanger ? 'Danger' : ""}
Leader: ${currentGroup.leaderName || "Unknown"} Status: ${currentGroup.status || "unknown"}
Role: ${currentGroup.role || "unassigned"} Task: ${currentGroup.currentTaskId || "None"}
${roster .map((member) => { const lifeState = ( member.lifeState || "unknown" ).replaceAll("_", " "); const leaderBadge = member.isLeader ? 'Leader' : ""; return `
${member.name || "Unknown Operator"} ${lifeState}
${member.uid || "No UID"} ${leaderBadge}
`; }) .join("")} `; }, renderActivity() { const listEl = document.getElementById("activityList"); if (!listEl) { return; } if (!this.activity.length) { listEl.innerHTML = '

No recent activity.

'; return; } listEl.innerHTML = this.activity .slice() .reverse() .slice(0, 8) .map( (entry) => `
${entry.type || "activity"} ${Math.round(entry.timestamp || 0)}s

${entry.message || ""}

`, ) .join(""); }, render() { this.updateDangerAlert(); this.updateRequestAlert(); this.syncLayoutState(); this.renderContracts(); this.renderRoster(); this.renderRequests(); this.renderActivity(); this.setActiveTab(this.activeTab); }, }; window.cadTasks.init();