forge/arma/client/addons/cad/ui/_site/cad-sidepanel.js
2026-05-23 09:23:12 -05:00

1 line
28 KiB
JavaScript

window.cadTasks={contracts:[],requests:[],groups:[],activity:[],session:{},mode:"operations",dispatchView:"board",activeTab:"contracts",selectedDispatchGroupId:"",selectedDispatchTaskId:"",selectedDispatchRequestId:"",selectedRosterMemberUid:"",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:!0},{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:!0},{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:!0},{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:!0},{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(e=>{e.addEventListener("click",()=>{this.setActiveTab(e.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",e=>{this.setHydratePayload(e||{})}),window.ForgeBridge.on("cad::assignment::response",e=>{this.handleServerResponse(!!e.success,e.message||"")}),window.ForgeBridge.on("cad::group::response",e=>{this.handleServerResponse(!!e.success,e.message||"")}),window.ForgeBridge.on("cad::request::response",e=>{this.handleServerResponse(!!e.success,e.message||"")}),window.ForgeBridge.ready({loaded:!0})},setActiveTab(e){this.activeTab=e||"contracts",document.querySelectorAll(".cad-tab").forEach(e=>{e.classList.toggle("is-active",e.dataset.tab===this.activeTab)}),document.querySelectorAll("[data-panel]").forEach(e=>{e.classList.toggle("is-active",e.dataset.panel===this.activeTab)})},syncLayoutState(){const e=document.querySelector(".cad-tabs"),t=document.getElementById("tabContractsBtn"),s=document.getElementById("tabRosterBtn"),a=document.getElementById("tabRequestsBtn"),n=document.getElementById("tabActivityBtn"),i=document.getElementById("contractsPanel"),r=document.getElementById("rosterPanel"),o=document.getElementById("requestsPanel"),d=document.getElementById("activityPanel"),c=i?.querySelector(".cad-section-header"),l=r?.querySelector(".cad-section-header");if(this.isDispatchMapMode())return e&&(e.style.display="",e.classList.remove("is-two-col"),e.classList.add("is-three-col")),t&&(t.style.display=""),s&&(s.textContent="Groups"),n&&(n.style.display="none"),a&&(a.style.display=""),d&&(d.classList.remove("is-active"),d.style.display="none"),o&&(o.style.display=""),r&&(r.style.display=""),l&&(l.textContent="Active Groups"),i&&(i.style.display=""),c&&(c.textContent="Contracts"),void(["contracts","roster","requests"].includes(this.activeTab)||(this.activeTab="contracts"));e&&(e.style.display="",e.classList.remove("is-three-col"),e.classList.remove("is-two-col")),t&&(t.style.display=""),s&&(s.textContent="Roster"),n&&(n.style.display=""),a&&(a.style.display=""),i&&(i.style.display=""),d&&(d.style.display=""),o&&(o.style.display=""),r&&(r.style.display=""),l&&(l.textContent="Roster"),c&&(c.textContent="Contracts")},setHydratePayload(e){this.contracts=Array.isArray(e.contracts)?e.contracts:[],this.requests=Array.isArray(e.requests)?e.requests:[],this.groups=Array.isArray(e.groups)?e.groups:[],this.activity=Array.isArray(e.activity)?e.activity:[],this.session=e.session&&"object"==typeof e.session?e.session:{},this.mode=e&&"string"==typeof e.mode?e.mode:"operations",this.dispatchView=e&&"string"==typeof e.dispatchView?e.dispatchView:"board";const t=document.getElementById("cadStatusMessage");if(!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),this.selectedDispatchGroupId&&!this.groups.some(e=>e.groupId===this.selectedDispatchGroupId)&&(this.selectedDispatchGroupId=""),this.selectedRosterMemberUid){this.groups.some(e=>this.normalizeCollection(e.members).some(e=>(e.uid||"")===this.selectedRosterMemberUid))||(this.selectedRosterMemberUid="")}this.selectedDispatchTaskId&&!this.contracts.some(e=>(e.taskId||e.taskID||"")===this.selectedDispatchTaskId)&&(this.selectedDispatchTaskId=""),this.selectedDispatchRequestId&&!this.requests.some(e=>(e.requestId||"")===this.selectedDispatchRequestId)&&(this.selectedDispatchRequestId=""),"dispatch"!==this.mode||"map"!==this.dispatchView||["contracts","roster","requests"].includes(this.activeTab)||(this.activeTab="contracts"),this.render()},setStatus(e,t){const s=document.getElementById("cadStatusMessage");s&&(s.textContent=e||"",s.dataset.type=t||"")},getDangerGroups(){return this.groups.filter(e=>"danger"===(e.status||""))},getSupportAlertRequests(){return this.requests.filter(e=>["medevac_9line","fire_support","air_support"].includes(e.type||""))},buildSupportAlertMessage(){const e=this.getSupportAlertRequests();if(!e.length)return"";return`Support request alert: ${e.map(e=>`${e.groupCallsign||e.groupId||"Unknown Group"} ${this.getRequestTypeLabel(e.type||"request")}`).join(", ")}`},getCurrentGroupCoordinates(){const e=this.getCurrentGroup(),t=Array.isArray(e?.position)?e.position:[0,0,0];return window.mapUI.formatPosition(t)},getSortedGroups(){return this.groups.slice().sort((e,t)=>{const s="danger"===(e.status||"")?0:1,a="danger"===(t.status||"")?0:1;if(s!==a)return s-a;const n=e.callsign||e.groupId||"",i=t.callsign||t.groupId||"";return n.localeCompare(i)})},isDispatchOrder:e=>!!e.isDispatchOrder||"dispatch_order"===(e.type||""),formatTypeLabel(e){const t=(e.type||"task").replaceAll("_"," ");return this.isDispatchOrder(e)?"dispatch order":t},getRequestDefinition(e){return this.requestTypes.find(t=>t.id===e)||null},getRequestTypeLabel(e){return this.getRequestDefinition(e)?.label||e},canSubmitSupportRequest(){return"operations"===this.mode&&this.isLeader()},openRequestModal(e){const t=this.getRequestDefinition(e);t&&(this.requestModalType=e,document.getElementById("cadRequestModalTitle").textContent=t.label,document.getElementById("cadRequestPrioritySelect").value=t.defaultPriority||"priority",this.renderRequestFields(t),document.getElementById("cadRequestModal").classList.remove("is-hidden"))},closeRequestModal(){this.requestModalType="",document.getElementById("cadRequestFields").innerHTML="",document.getElementById("cadRequestModal").classList.add("is-hidden")},renderRequestFields(e){const t=document.getElementById("cadRequestFields");if(!t||!e)return;const s=this.getCurrentGroupCoordinates();t.innerHTML=e.fields.map(e=>{const t=e.defaultFromGroupPosition?s:"";return"select"===e.type?`\n <label class="cad-field">\n <span>${e.label}</span>\n <select id="cadRequestField_${e.id}" class="cad-select">\n ${e.options.map(e=>`<option value="${e}">${e.replaceAll("_"," ")}</option>`).join("")}\n </select>\n </label>\n `:"textarea"===e.type?`\n <label class="cad-field">\n <span>${e.label}</span>\n <textarea id="cadRequestField_${e.id}" class="cad-textarea" rows="3">${t}</textarea>\n </label>\n `:`\n <label class="cad-field">\n <span>${e.label}</span>\n <input id="cadRequestField_${e.id}" class="cad-input" type="text" value="${t}" />\n </label>\n `}).join("")},submitSupportRequest(){const e=this.getRequestDefinition(this.requestModalType);if(!e)return;const t={};e.fields.forEach(e=>{const s=document.getElementById(`cadRequestField_${e.id}`);t[e.id]=s?String(s.value||"").trim():""});const s=document.getElementById("cadRequestPrioritySelect").value;this.setStatus("Submitting support request...","info"),window.mapUI.sendEvent("cad::supportRequest::submit",{type:e.id,fields:t,priority:s}),this.closeRequestModal()},closeSupportRequest(e){e&&(this.setStatus(this.isDispatchMode()?"Closing support request...":"Cancelling support request...","info"),window.mapUI.sendEvent("cad::supportRequest::close",{requestID:e}))},renderRequests(){const e=document.getElementById("requestList");if(!e)return;if(this.isDispatchMapMode()){const t=this.requests.slice().sort((e,t)=>{const s=e.title||e.requestId||"",a=t.title||t.requestId||"";return s.localeCompare(a)});return t.length?void(e.innerHTML=t.map(e=>{const t=e.requestId||"",s=Array.isArray(e.position)?e.position:[0,0,0];return`\n <button\n type="button"\n class="task-card dispatch-map-card ${t===this.selectedDispatchRequestId?"is-selected":""} ${["medevac_9line","fire_support","air_support"].includes(e.type||"")?"is-warning":""}"\n data-request-id="${t}"\n onclick="window.cadTasks.focusRequest('${t}')"\n >\n <div class="task-card-header">\n <strong>${e.title||t||"Support Request"}</strong>\n <span class="task-type">${this.getRequestTypeLabel(e.type||"request")}</span>\n </div>\n <p class="task-description">${e.summary||""}</p>\n <div class="task-meta">\n <span>Group: ${e.groupCallsign||e.groupId||"Unknown"}</span>\n <span>${(e.priority||"priority").replaceAll("_"," ")}</span>\n </div>\n <div class="task-meta">\n <span>${window.mapUI.formatPosition(s)}</span>\n <span>${t||"request"}</span>\n </div>\n </button>\n `}).join("")):void(e.innerHTML='<div class="placeholder-message"><p>No support requests are currently active.</p></div>')}const t=this.canSubmitSupportRequest()?`\n <div class="cad-request-actions">\n ${this.requestTypes.map(e=>`\n <button\n type="button"\n class="task-secondary-btn cad-request-btn"\n onclick="window.cadTasks.openRequestModal('${e.id}')"\n >\n ${e.label}\n </button>\n `).join("")}\n </div>\n `:"";this.requests.length?e.innerHTML=`\n ${t}\n ${this.requests.map(e=>{const t=this.isLeader()&&(e.groupId||"")===this.getPlayerGroupId(),s=this.canDispatch()||t,a=this.isDispatchMode()?"Close":"Cancel",n=e.requestId||"";return`\n <div\n class="task-card cad-request-card dispatch-map-card ${n===this.selectedDispatchRequestId?"is-selected":""}"\n data-request-id="${n}"\n role="button"\n tabindex="0"\n onclick="window.cadTasks.focusRequest('${n}')"\n onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.cadTasks.focusRequest('${n}'); }"\n >\n <div class="task-card-header">\n <strong>${e.title||this.getRequestTypeLabel(e.type||"")}</strong>\n <span class="task-type">${(e.priority||"priority").replaceAll("_"," ")}</span>\n </div>\n <p class="task-description">${e.summary||""}</p>\n <div class="task-meta">\n <span>Group: ${e.groupCallsign||e.groupId||"Unknown"}</span>\n <span>${this.getRequestTypeLabel(e.type||"")}</span>\n </div>\n ${s?`<div class="task-action-row">\n <button type="button" class="task-secondary-btn" onclick="event.stopPropagation(); window.cadTasks.closeSupportRequest('${n}')">${a}</button>\n </div>`:""}\n </div>\n `}).join("")}\n `:e.innerHTML=`\n ${t}\n <div class="placeholder-message"><p>No support requests are currently active.</p></div>\n `},updateDangerAlert(){const e=document.getElementById("cadDangerAlert");if(!e)return;if(!this.isDispatchMapMode())return e.textContent="",void e.classList.add("is-hidden");const t=this.getDangerGroups();if(!t.length)return e.textContent="",void e.classList.add("is-hidden");const s=t.map(e=>e.callsign||e.groupId||"Unknown Group");e.textContent=`Danger alert active: ${s.join(", ")}`,e.classList.remove("is-hidden")},updateRequestAlert(){const e=document.getElementById("cadRequestAlert");if(!e)return;if(!this.isDispatchMapMode())return e.textContent="",void e.classList.add("is-hidden");const t=this.buildSupportAlertMessage();if(!t)return e.textContent="",void e.classList.add("is-hidden");e.textContent=t,e.classList.remove("is-hidden")},clearFocusStatusSoon(e){this.focusStatusTimer&&window.clearTimeout(this.focusStatusTimer),this.focusStatusTimer=window.setTimeout(()=>{const t=document.getElementById("cadStatusMessage");t&&"info"===t.dataset.type&&t.textContent===e&&this.setStatus("","")},1800)},handleServerResponse(e,t){this.setStatus(t||(e?"CAD update succeeded.":"CAD update failed."),e?"success":"error")},acknowledgeTask(e){this.setStatus("Acknowledging contract...","info"),window.mapUI.sendEvent("cad::tasks::acknowledge",{taskID:e})},declineTask(e){this.setStatus("Declining contract...","info"),window.mapUI.sendEvent("cad::tasks::decline",{taskID:e})},updateGroupStatus(e,t){this.setStatus("Updating group status...","info"),window.mapUI.sendEvent("cad::groups::status",{groupID:e,status:t})},updateGroupRole(e,t){this.setStatus("Updating group role...","info"),window.mapUI.sendEvent("cad::groups::role",{groupID:e,role:t})},focusGroup(e){const t=this.groups.find(t=>t.groupId===e);if(!t)return void this.setStatus("Selected group is no longer available.","error");this.selectedDispatchGroupId=e,this.selectedDispatchTaskId="",this.selectedDispatchRequestId="",this.selectedRosterMemberUid="";const s=`Centering map on ${t.callsign||t.groupId||"group"}...`;this.setStatus(s,"info"),this.clearFocusStatusSoon(s),window.mapUI.sendEvent("cad::groups::focus",{groupID:e}),this.render()},focusMember(e){let t=null;if(this.groups.some(s=>this.normalizeCollection(s.members).some(s=>(s.uid||"")===e&&(t=s,!0))),!t)return void this.setStatus("Selected group member is no longer available.","error");if((Array.isArray(t.position)?t.position:[]).length<2)return void this.setStatus("Selected group member has no map position.","error");this.selectedRosterMemberUid=e,this.selectedDispatchGroupId="",this.selectedDispatchTaskId="",this.selectedDispatchRequestId="";const s=`Centering map on ${t.name||"group member"}...`;this.setStatus(s,"info"),this.clearFocusStatusSoon(s),window.mapUI.sendEvent("cad::members::focus",{uid:e}),this.render()},focusTask(e){const t=this.contracts.find(t=>(t.taskId||t.taskID||"")===e);if(!t)return void this.setStatus("Selected contract is no longer available.","error");this.selectedDispatchTaskId=e,this.selectedDispatchGroupId="",this.selectedDispatchRequestId="",this.selectedRosterMemberUid="";const s=`Centering map on ${t.title||e}...`;this.setStatus(s,"info"),this.clearFocusStatusSoon(s),window.mapUI.sendEvent("cad::tasks::focus",{taskID:e}),this.render()},focusRequest(e){const t=this.requests.find(t=>(t.requestId||"")===e);if(!t)return void this.setStatus("Selected request is no longer available.","error");if((Array.isArray(t.position)?t.position:[]).length<2)return void this.setStatus("Selected request has no map position.","error");this.selectedDispatchRequestId=e,this.selectedDispatchGroupId="",this.selectedDispatchTaskId="",this.selectedRosterMemberUid="";const s=`Centering map on ${t.title||e}...`;this.setStatus(s,"info"),this.clearFocusStatusSoon(s),window.mapUI.sendEvent("cad::requests::focus",{requestID:e}),this.render()},getPlayerGroupId(){return this.session.groupId||""},getCurrentGroup(){const e=this.getPlayerGroupId();return this.groups.find(t=>t.groupId===e)||null},normalizeCollection:e=>Array.isArray(e)?e:e&&"object"==typeof e?Object.values(e):[],canDispatch(){return!!this.session.isDispatcher},isDispatchMode(){return"dispatch"===this.mode},isDispatchMapMode(){return"dispatch"===this.mode&&"map"===this.dispatchView},isLeader(){return!!this.session.isLeader},renderContracts(){const e=document.getElementById("taskList");if(!e)return;if(this.isDispatchMapMode()){if(!this.contracts.length)return void(e.innerHTML='<div class="placeholder-message"><p>No contracts are currently available.</p></div>');const t=this.contracts.slice().sort((e,t)=>{const s="unassigned"===(e.assignmentState||"unassigned")?0:1,a="unassigned"===(t.assignmentState||"unassigned")?0:1;if(s!==a)return s-a;const n=e.taskId||e.taskID||"",i=t.taskId||t.taskID||"";return n.localeCompare(i)});return void(e.innerHTML=t.map(e=>{const t=e.taskId||e.taskID||"",s=Array.isArray(e.position)?e.position:[0,0,0],a=e.assignedGroupId||"",n=e.assignmentState||"unassigned",i=this.groups.find(e=>e.groupId===a),r=t===this.selectedDispatchTaskId,o="unassigned"===n?"Unassigned":`${n}: ${i?i.callsign:a||"Unknown"}`;return`\n <button\n type="button"\n class="task-card dispatch-map-card ${r?"is-selected":""}"\n data-task-id="${t}"\n onclick="window.cadTasks.focusTask('${t}')"\n >\n <div class="task-card-header">\n <strong>${e.title||t}</strong>\n <span class="task-type">${this.formatTypeLabel(e)}</span>\n </div>\n <p class="task-description">${e.description||""}</p>\n <div class="task-meta">\n <span>${o}</span>\n <span>${window.mapUI.formatPosition(s)}</span>\n </div>\n </button>\n `}).join(""))}const t=this.getPlayerGroupId(),s=this.contracts.filter(e=>(e.assignedGroupId||"")===t);s.length?e.innerHTML=s.map(e=>{const s=e.taskId||e.taskID||"",a=Array.isArray(e.position)?e.position:[0,0,0],n=e.assignedGroupId||"",i=e.assignmentState||"unassigned",r=this.groups.find(e=>e.groupId===n),o=this.isLeader()&&n===t;return`\n <div\n class="task-card dispatch-map-card ${s===this.selectedDispatchTaskId?"is-selected":""}"\n data-task-id="${s}"\n role="button"\n tabindex="0"\n onclick="window.cadTasks.focusTask('${s}')"\n onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.cadTasks.focusTask('${s}'); }"\n >\n <div class="task-card-header">\n <strong>${e.title||s}</strong>\n <span class="task-type">${this.formatTypeLabel(e)}</span>\n </div>\n <p class="task-description">${e.description||""}</p>\n <div class="task-meta">\n <span>${"unassigned"===i?"Available":`${i}: ${r?r.callsign:n}`}</span>\n <span>${window.mapUI.formatPosition(a)}</span>\n </div>\n ${o&&"assigned"===i?`<div class="task-action-row">\n <button type="button" class="task-accept-btn" onclick="event.stopPropagation(); window.cadTasks.acknowledgeTask('${s}')">Acknowledge</button>\n <button type="button" class="task-secondary-btn" onclick="event.stopPropagation(); window.cadTasks.declineTask('${s}')">Decline</button>\n </div>`:""}\n </div>\n `}).join(""):e.innerHTML='<div class="placeholder-message"><p>No contract is currently assigned to your group.</p></div>'},renderRoster(){const e=document.getElementById("rosterList");if(!e)return;if(this.isDispatchMapMode())return this.groups.length?void(e.innerHTML=this.getSortedGroups().map(e=>{const t=(e.groupId||"")===this.selectedDispatchGroupId,s="danger"===(e.status||"");return`\n <button\n type="button"\n class="task-card roster-member-card dispatch-map-group-card ${t?"is-selected":""} ${s?"is-danger":""}"\n data-group-id="${e.groupId||""}"\n onclick="window.cadTasks.focusGroup('${e.groupId||""}')"\n >\n <div class="task-card-header">\n <strong>${e.callsign||e.groupId||"Unknown Group"}</strong>\n <span class="task-type">${e.role||"group"}</span>\n ${s?'<span class="task-alert-badge">Danger</span>':""}\n </div>\n <div class="task-meta">\n <span>Leader: ${e.leaderName||"Unknown"}</span>\n <span>Status: ${e.status||"unknown"}</span>\n </div>\n <div class="task-meta">\n <span>Members: ${this.normalizeCollection(e.members).length}</span>\n <span>Task: ${e.currentTaskId||"None"}</span>\n </div>\n </button>\n `}).join("")):void(e.innerHTML='<div class="placeholder-message"><p>No active groups are currently available.</p></div>');const t=this.getCurrentGroup();if(!t)return void(e.innerHTML='<div class="placeholder-message"><p>Your group is not currently available.</p></div>');const s=this.normalizeCollection(t.members),a="danger"===(t.status||"");s.length?e.innerHTML=`\n <div class="roster-summary-card ${a?"is-danger":""}">\n <div class="task-card-header">\n <strong>${t.callsign||t.groupId||"Current Group"}</strong>\n <span class="task-type">${s.length} member${1===s.length?"":"s"}</span>\n ${a?'<span class="task-alert-badge">Danger</span>':""}\n </div>\n <div class="task-meta">\n <span>Leader: ${t.leaderName||"Unknown"}</span>\n <span>Status: ${t.status||"unknown"}</span>\n </div>\n <div class="task-meta">\n <span>Role: ${t.role||"unassigned"}</span>\n <span>Task: ${t.currentTaskId||"None"}</span>\n </div>\n </div>\n ${s.map(e=>{const t=(e.lifeState||"unknown").replaceAll("_"," "),s=e.isLeader?'<span class="roster-leader-badge">Leader</span>':"",a=e.uid||"";return`\n <div\n class="task-card roster-member-card dispatch-map-group-card ${a&&a===this.selectedRosterMemberUid?"is-selected":""}"\n data-member-id="${a}"\n role="button"\n tabindex="0"\n onclick="window.cadTasks.focusMember('${a}')"\n onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.cadTasks.focusMember('${a}'); }"\n >\n <div class="task-card-header">\n <strong>${e.name||"Unknown Operator"}</strong>\n <span class="task-type">${t}</span>\n </div>\n <div class="task-meta">\n <span>${e.uid||"No UID"}</span>\n <span>${s}</span>\n </div>\n </div>\n `}).join("")}\n `:e.innerHTML='<div class="placeholder-message"><p>No roster members are currently available.</p></div>'},renderActivity(){const e=document.getElementById("activityList");e&&(this.activity.length?e.innerHTML=this.activity.slice().reverse().slice(0,8).map(e=>`\n <div class="task-card">\n <div class="task-card-header">\n <strong>${e.type||"activity"}</strong>\n <span class="task-type">${Math.round(e.timestamp||0)}s</span>\n </div>\n <p class="task-description">${e.message||""}</p>\n </div>\n `).join(""):e.innerHTML='<div class="placeholder-message"><p>No recent activity.</p></div>')},render(){this.updateDangerAlert(),this.updateRequestAlert(),this.syncLayoutState(),this.renderContracts(),this.renderRoster(),this.renderRequests(),this.renderActivity(),this.setActiveTab(this.activeTab)}},window.cadTasks.init();