- Add a dispatcher web view and layout switching - Route group role updates and dispatcher hydration through the UI bridge - Refresh top bar and hide map/side panel outside operations mode
1 line
9.4 KiB
JavaScript
1 line
9.4 KiB
JavaScript
window.cadDispatcher={contracts:[],groups:[],activity:[],session:{},editingGroupId:"",statuses:["available","en_route","on_task","holding","danger","refit","offline"],roles:["infantry","recon","armor","air","logistics","support"],init(){document.getElementById("dispatcherRefreshBtn").addEventListener("click",()=>{this.setStatus("Refreshing board...","info"),window.mapUI.sendEvent("cad::refresh",{})}),document.getElementById("dispatcherGroupModalCloseBtn").addEventListener("click",()=>{this.closeGroupModal()}),document.getElementById("dispatcherGroupModalSaveBtn").addEventListener("click",()=>{this.applyGroupUpdates()}),document.querySelector("#dispatcherGroupModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeGroupModal()}),window.mapUI.sendEvent("cad::dispatcher::ready",{})},receiveHydrate(t){this.contracts=Array.isArray(t.contracts)?t.contracts:[],this.groups=Array.isArray(t.groups)?t.groups:[],this.activity=Array.isArray(t.activity)?t.activity:[],this.session=t.session&&"object"==typeof t.session?t.session:{};const e=document.getElementById("dispatcherStatusMessage");!e||e.dataset.type&&"info"!==e.dataset.type||this.setStatus("",""),this.syncOpenModal(),this.render()},setStatus(t,e){const s=document.getElementById("dispatcherStatusMessage");s&&(s.textContent=t||"",s.dataset.type=e||"")},assignTask(t){const e=document.getElementById(`dispatcher-assign-group-${t}`);e&&e.value?(this.setStatus("Submitting assignment...","info"),window.mapUI.sendEvent("cad::tasks::assign",{taskID:t,groupID:e.value,note:""})):this.setStatus("Select a group before assigning a contract.","error")},openGroupModal(t){const e=this.groups.find(e=>e.groupId===t);e&&(this.editingGroupId=t,document.getElementById("dispatcherModalGroupCallsign").textContent=e.callsign||e.groupId||"Unknown",document.getElementById("dispatcherModalGroupLeader").textContent=e.leaderName||"Unknown",document.getElementById("dispatcherModalGroupTask").textContent=e.currentTaskId||"None",document.getElementById("dispatcherModalGroupOrg").textContent=e.orgId||"default",document.getElementById("dispatcherModalRoleSelect").innerHTML=this.roles.map(t=>`<option value="${t}" ${t===e.role?"selected":""}>${t.replaceAll("_"," ")}</option>`).join(""),document.getElementById("dispatcherModalStatusSelect").innerHTML=this.statuses.map(t=>`<option value="${t}" ${t===e.status?"selected":""}>${t.replaceAll("_"," ")}</option>`).join(""),document.getElementById("dispatcherGroupModal").classList.remove("is-hidden"))},closeGroupModal(){this.editingGroupId="",document.getElementById("dispatcherGroupModal").classList.add("is-hidden")},syncOpenModal(){if(!this.editingGroupId)return;const t=this.groups.find(t=>t.groupId===this.editingGroupId);t?(document.getElementById("dispatcherModalGroupCallsign").textContent=t.callsign||t.groupId||"Unknown",document.getElementById("dispatcherModalGroupLeader").textContent=t.leaderName||"Unknown",document.getElementById("dispatcherModalGroupTask").textContent=t.currentTaskId||"None",document.getElementById("dispatcherModalGroupOrg").textContent=t.orgId||"default"):this.closeGroupModal()},applyGroupUpdates(){if(!this.editingGroupId)return;const t=this.groups.find(t=>t.groupId===this.editingGroupId);if(!t)return void this.closeGroupModal();const e=document.getElementById("dispatcherModalRoleSelect").value,s=document.getElementById("dispatcherModalStatusSelect").value;let n=!1;e&&e!==(t.role||"")&&(n=!0,this.setStatus("Updating group role...","info"),window.mapUI.sendEvent("cad::groups::role",{groupID:this.editingGroupId,role:e})),s&&s!==(t.status||"")&&(n=!0,this.setStatus("Updating group status...","info"),window.mapUI.sendEvent("cad::groups::status",{groupID:this.editingGroupId,status:s})),n||this.setStatus("No group changes to save.","info"),this.closeGroupModal()},buildGroupEditorButton:t=>`\n <button\n type="button"\n class="dispatch-icon-btn"\n onclick="window.cadDispatcher.openGroupModal('${t}')"\n aria-label="Edit group"\n title="Edit group"\n >\n ⚙\n </button>\n `,renderMetrics(){const t=this.contracts.filter(t=>"unassigned"!==(t.assignmentState||"unassigned")),e=this.contracts.filter(t=>"unassigned"===(t.assignmentState||"unassigned")),s=this.groups.filter(t=>"danger"===(t.status||""));document.getElementById("metricOpenContracts").textContent=e.length,document.getElementById("metricAssignedContracts").textContent=t.length,document.getElementById("metricActiveGroups").textContent=this.groups.length,document.getElementById("metricDangerGroups").textContent=s.length},renderOpenContracts(){const t=document.getElementById("dispatcherOpenContracts"),e=this.contracts.filter(t=>"unassigned"===(t.assignmentState||"unassigned"));if(!e.length)return void(t.innerHTML='<div class="placeholder-message"><p>No open contracts.</p></div>');const s=this.groups.map(t=>`<option value="${t.groupId}">${t.callsign||t.groupId}</option>`).join("");t.innerHTML=e.map(t=>{const e=t.taskId||t.taskID||"",n=Array.isArray(t.position)?t.position:[0,0,0];return`\n <article class="dispatch-card">\n <header class="dispatch-card-header">\n <strong>${t.title||e}</strong>\n <span class="dispatch-badge">${t.type||"task"}</span>\n </header>\n <p class="dispatch-description">${t.description||""}</p>\n <div class="dispatch-meta">\n <span>Unassigned</span>\n <span>X: ${Math.round(n[0]||0)} Y: ${Math.round(n[1]||0)}</span>\n </div>\n <div class="dispatch-actions">\n <select id="dispatcher-assign-group-${e}" class="dispatch-select">\n <option value="">Assign to group</option>\n ${s}\n </select>\n <button type="button" class="dispatch-btn" onclick="window.cadDispatcher.assignTask('${e}')">Assign</button>\n </div>\n </article>\n `}).join("")},renderAssignedContracts(){const t=document.getElementById("dispatcherAssignedContracts"),e=this.contracts.filter(t=>"unassigned"!==(t.assignmentState||"unassigned"));e.length?t.innerHTML=e.map(t=>{const e=t.taskId||t.taskID||"",s=this.groups.find(e=>e.groupId===(t.assignedGroupId||""));return`\n <article class="dispatch-card">\n <header class="dispatch-card-header">\n <strong>${t.title||e}</strong>\n <span class="dispatch-badge">${t.assignmentState||"assigned"}</span>\n </header>\n <p class="dispatch-description">${t.description||""}</p>\n <div class="dispatch-meta">\n <span>Group: ${s?s.callsign:t.assignedGroupId||"Unknown"}</span>\n <span>Type: ${t.type||"task"}</span>\n </div>\n </article>\n `}).join(""):t.innerHTML='<div class="placeholder-message"><p>No assigned contracts.</p></div>'},renderGroups(){const t=document.getElementById("dispatcherGroups");this.groups.length?t.innerHTML=this.groups.map(t=>`\n <article class="dispatch-card dispatch-card-group">\n <header class="dispatch-card-header">\n <div class="dispatch-card-header-main">\n <strong>${t.callsign||t.groupId}</strong>\n <span class="dispatch-badge">${t.role||"group"}</span>\n </div>\n <div class="dispatch-card-header-actions">\n ${this.buildGroupEditorButton(t.groupId)}\n </div>\n </header>\n <div class="dispatch-meta">\n <span>Leader: ${t.leaderName||"Unknown"}</span>\n <span>Status: ${t.status||"unknown"}</span>\n </div>\n <div class="dispatch-meta">\n <span>Org: ${t.orgId||"default"}</span>\n <span>Task: ${t.currentTaskId||"None"}</span>\n </div>\n </article>\n `).join(""):t.innerHTML='<div class="placeholder-message"><p>No active groups available.</p></div>'},renderActivity(){const t=document.getElementById("dispatcherActivity");this.activity.length?t.innerHTML=this.activity.slice().reverse().slice(0,12).map(t=>`\n <article class="dispatch-card">\n <header class="dispatch-card-header">\n <strong>${t.type||"activity"}</strong>\n <span class="dispatch-badge">${Math.round(t.timestamp||0)}s</span>\n </header>\n <p class="dispatch-description">${t.message||""}</p>\n </article>\n `).join(""):t.innerHTML='<div class="placeholder-message"><p>No recent activity.</p></div>'},render(){this.renderMetrics(),this.renderOpenContracts(),this.renderAssignedContracts(),this.renderGroups(),this.renderActivity()}},window.cadDispatcher.init(); |