From e688d9426b654f8dd587381e46d0f72beccd9878 Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Sat, 23 May 2026 02:10:34 -0500 Subject: [PATCH] Add CAD mission task generation flow - Wire dispatcher UI events into a new generated task request path - Add bridge/server handling for generated mission task requests - Update mission generators and dispatcher UI for task metadata --- .../cad/functions/fnc_handleUIEvents.sqf | 10 ++ .../addons/cad/functions/fnc_initUIBridge.sqf | 11 ++ .../addons/cad/ui/_site/cad-dispatcher.css | 2 +- .../addons/cad/ui/_site/cad-dispatcher.js | 2 +- .../addons/cad/ui/_site/dispatcher.html | 2 +- arma/client/addons/cad/ui/src/dispatcher.html | 72 ++++++++-- .../cad/ui/src/dispatcher/formatters.js | 9 ++ .../addons/cad/ui/src/dispatcher/index.js | 52 +++++++ .../addons/cad/ui/src/dispatcher/modals.js | 23 +++ .../addons/cad/ui/src/styles/dispatcher.css | 12 ++ .../CfgFunctions.hpp | 1 + .../fn_attackMissionGenerator.sqf | 3 +- .../fn_captureHvtMissionGenerator.sqf | 3 +- .../fn_defendMissionGenerator.sqf | 3 +- .../fn_defuseMissionGenerator.sqf | 3 +- .../fn_deliveryMissionGenerator.sqf | 6 +- .../fn_destroyMissionGenerator.sqf | 3 +- .../fn_hostageMissionGenerator.sqf | 12 +- .../fn_hvtMissionGenerator.sqf | 3 +- .../functions/missionManager/README.md | 5 + .../missionManager/fn_requestMissionTask.sqf | 134 ++++++++++++++++++ arma/server/addons/cad/XEH_preInit.sqf | 42 ++++++ 22 files changed, 389 insertions(+), 24 deletions(-) create mode 100644 arma/forge_pmc_simulator.Tanoa/functions/missionManager/fn_requestMissionTask.sqf diff --git a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf index 8f78075..d7bca1a 100644 --- a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf @@ -88,6 +88,16 @@ switch (_event) do { GVAR(CADUIBridge) call ["requestCreateDispatchOrder", [_assigneeGroupID, _targetGroupID, _note, _priority, _request]]; }; + case "cad::generatedTask::request": { + private _taskType = ""; + private _metadata = createHashMap; + if (_data isEqualType createHashMap) then { + _taskType = _data getOrDefault ["taskType", ""]; + _metadata = _data getOrDefault ["metadata", createHashMap]; + }; + + GVAR(CADUIBridge) call ["requestGeneratedMissionTask", [_taskType, _metadata]]; + }; case "cad::supportRequest::submit": { private _type = ""; private _fields = createHashMap; diff --git a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf index b72aa4a..e59768a 100644 --- a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf @@ -227,6 +227,17 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ [SRPC(cad,requestCreateCadDispatchOrder), [getPlayerUID player, _assigneeGroupID, _targetGroupID, _note, _priority, _request]] call CFUNC(serverEvent); true }], + ["requestGeneratedMissionTask", compileFinal { + params [ + ["_taskType", "", [""]], + ["_metadata", createHashMap, [createHashMap]] + ]; + + if (_taskType isEqualTo "") exitWith { false }; + + [SRPC(cad,requestGenerateCadMissionTask), [getPlayerUID player, _taskType, _metadata]] call CFUNC(serverEvent); + true + }], ["requestSubmitSupportRequest", compileFinal { params [ ["_type", "", [""]], diff --git a/arma/client/addons/cad/ui/_site/cad-dispatcher.css b/arma/client/addons/cad/ui/_site/cad-dispatcher.css index c65da95..f09c4d1 100644 --- a/arma/client/addons/cad/ui/_site/cad-dispatcher.css +++ b/arma/client/addons/cad/ui/_site/cad-dispatcher.css @@ -1 +1 @@ -html,body{background:radial-gradient(circle at 0 0,#29455d2e,#0000 30%),linear-gradient(#090e14f5,#0f161ffa);width:100%;height:100%;margin:0;padding:0;overflow:hidden}body{color:var(--text);font-family:var(--font)}.dispatch-shell{flex-direction:column;gap:14px;height:100%;padding:18px;display:flex}.dispatch-header{justify-content:space-between;align-items:center;gap:16px;display:flex}.dispatch-kicker{color:var(--accent);text-transform:uppercase;letter-spacing:.1em;margin:0 0 4px;font-size:11px;font-weight:700}.dispatch-header h2{margin:0;font-size:24px;font-weight:650}.dispatch-header button,.dispatch-btn,.dispatch-select{color:var(--text);background:#181f28e6;border:1px solid #ffffff1f}.dispatch-header button,.dispatch-btn{cursor:pointer;padding:10px 14px}.dispatch-btn-secondary{background:#352827eb}.dispatch-status{color:#e9f1f8c7;min-height:20px;font-size:13px}.dispatch-status[data-type=success]{color:#79d28a}.dispatch-status[data-type=error]{color:#ff8a80}.dispatch-danger-alert{color:#ffd4cf;letter-spacing:.06em;text-transform:uppercase;background:linear-gradient(90deg,#5c1212f0,#801d1dd1);border:1px solid #ff6b6b61;padding:10px 12px;font-size:12px;font-weight:700;animation:1.35s ease-in-out infinite cad-danger-pulse}.dispatch-danger-alert.is-hidden{display:none}.dispatch-warning-alert{color:#ffe9b2;letter-spacing:.06em;text-transform:uppercase;background:linear-gradient(90deg,#59400cf0,#7d5c12d6);border:1px solid #f6c6546b;padding:10px 12px;font-size:12px;font-weight:700;animation:1.35s ease-in-out infinite cad-warning-pulse}.dispatch-warning-alert.is-hidden{display:none}.dispatch-metrics{grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;display:grid}.metric-card{background:#0d131ab8;border:1px solid #ffffff14;padding:14px}.metric-label{color:#e9f1f899;text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;font-size:11px;display:block}.metric-card strong{font-size:28px;font-weight:700}.metric-card.is-danger{background:linear-gradient(#4a1111db,#160d10eb);border-color:#ff6b6b57;animation:1.35s ease-in-out infinite cad-danger-pulse;box-shadow:inset 0 0 0 1px #ff6b6b1f}.metric-card.is-warning{background:linear-gradient(#5c410edb,#1d160beb);border-color:#f6c65457;animation:1.35s ease-in-out infinite cad-warning-pulse;box-shadow:inset 0 0 0 1px #f6c6541f}.dispatch-grid{flex:1;grid-template-columns:repeat(12,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);gap:14px;min-height:0;display:grid}.dispatch-panel{background:#0b1118c7;border:1px solid #ffffff14;flex-direction:column;min-width:0;min-height:0;display:flex}.dispatch-panel-open{grid-column:span 5}.dispatch-panel-assigned{grid-column:span 7}.dispatch-panel-groups{grid-column:span 8}.dispatch-panel-activity{grid-column:span 4}.dispatch-panel-header{border-bottom:1px solid #ffffff14;justify-content:space-between;align-items:center;padding:12px 14px;display:flex}.dispatch-panel-header h3{text-transform:uppercase;letter-spacing:.08em;color:var(--accent);margin:0;font-size:13px}.dispatch-list{flex-direction:column;flex:1;gap:10px;padding:12px;display:flex;overflow:auto}.dispatch-inline-section{flex-direction:column;gap:10px;display:flex}.dispatch-inline-header{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;font-size:11px;font-weight:700}.dispatch-card{background:#131a22b8;border:1px solid #ffffff0f;padding:12px}.dispatch-card-interactive{cursor:pointer}.dispatch-card-interactive:hover{background:#171f28d1;border-color:#5bbbff33}.dispatch-card-header,.dispatch-meta{justify-content:space-between;gap:10px;display:flex}.dispatch-card-header-actions{align-items:center;gap:8px;display:flex}.dispatch-card-header-main{align-items:center;gap:8px;min-width:0;display:flex}.dispatch-card-header{margin-bottom:8px}.dispatch-description{color:#f1f6fbd1;margin:0 0 10px;font-size:13px;line-height:1.45}.dispatch-meta{color:#e5edf4b3;margin-bottom:10px;font-size:12px}.dispatch-badge{color:var(--accent);text-transform:uppercase;background:#102b3db3;border:1px solid #5bbbff2e;padding:3px 7px;font-size:11px}.dispatch-alert-badge{color:#ffd8d1;text-transform:uppercase;letter-spacing:.08em;background:#5f1717e0;border:1px solid #ff6b6b70;padding:3px 7px;font-size:11px;font-weight:700}.dispatch-icon-btn{width:32px;height:32px;color:var(--text);cursor:pointer;background:#181f28eb;border:1px solid #ffffff24;padding:0}.dispatch-icon-btn:hover{background:#202a34f5}.dispatch-actions{flex-direction:column;gap:8px;display:flex}.dispatch-card.is-danger{background:linear-gradient(#451416c7,#1c1115eb);border-color:#ff6b6b57;animation:1.35s ease-in-out infinite cad-danger-pulse;box-shadow:inset 0 0 0 1px #ff6b6b1a}.dispatch-card.is-danger .dispatch-meta,.dispatch-card.is-danger .dispatch-description{color:#ffe8e4d1}.dispatch-card.is-warning{background:linear-gradient(#564011c7,#221b10eb);border-color:#f6c65457;animation:1.35s ease-in-out infinite cad-warning-pulse;box-shadow:inset 0 0 0 1px #f6c6541a}.dispatch-card.is-warning .dispatch-meta,.dispatch-card.is-warning .dispatch-description{color:#fff3d6d6}.dispatch-actions-split{margin-top:10px}.dispatch-select{width:100%;padding:9px 10px}.dispatch-textarea{width:100%;min-height:92px;color:var(--text);font:inherit;resize:vertical;box-sizing:border-box;background:#181f28eb;border:1px solid #ffffff1f;padding:10px 12px}.placeholder-message{text-align:center;color:#e9f1f899;padding:18px}.dispatch-modal{z-index:30;box-sizing:border-box;justify-content:center;align-items:center;padding:32px 24px;display:flex;position:fixed;inset:0}.dispatch-modal.is-hidden{display:none}.dispatch-modal-backdrop{background:#04080cb8;position:absolute;inset:0}.dispatch-modal-dialog{background:#0b1118fa;border:1px solid #ffffff1f;flex-direction:column;width:min(560px,100% - 48px);max-height:calc(100vh - 64px);margin:0;display:flex;position:relative;box-shadow:0 24px 64px #0000006b}.dispatch-modal-header,.dispatch-modal-actions{justify-content:space-between;align-items:center;gap:12px;padding:14px 16px;display:flex}.dispatch-modal-header{border-bottom:1px solid #ffffff14}.dispatch-modal-header h3{margin:0;font-size:22px;font-weight:650}.dispatch-modal-body{flex:1;min-height:0;padding:16px;overflow:auto}.dispatch-meta-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-bottom:18px;display:grid}.dispatch-meta-grid strong{margin-top:4px;font-size:14px;font-weight:600;display:block}.dispatch-modal-fields{gap:12px;display:grid}.dispatch-field{gap:6px;display:grid}.dispatch-field span{text-transform:uppercase;letter-spacing:.08em;color:#e9f1f8b3;font-size:12px;font-weight:650}.dispatch-modal-actions{border-top:1px solid #ffffff14;justify-content:flex-end}.dispatch-detail-block,.dispatch-detail-list{background:#131a22b8;border:1px solid #ffffff14}.dispatch-detail-block{color:#f1f6fbd1;white-space:pre-wrap;padding:12px;line-height:1.45}.dispatch-detail-list{gap:1px;display:grid;overflow:hidden}.dispatch-detail-row{background:#0e141ceb;grid-template-columns:minmax(0,180px) minmax(0,1fr);gap:12px;padding:10px 12px;display:grid}.dispatch-detail-label{color:#e9f1f8a3;text-transform:uppercase;letter-spacing:.06em;font-size:12px;font-weight:650}.dispatch-detail-value{color:#f1f6fbd6;word-break:break-word;white-space:pre-wrap;line-height:1.4}@keyframes cad-danger-pulse{0%,to{box-shadow:inset 0 0 0 1px #ff6b6b14,0 0 #ff6b6b00}50%{box-shadow:inset 0 0 0 1px #ff8d8d38,0 0 18px #ff6b6b29}}@keyframes cad-warning-pulse{0%,to{box-shadow:inset 0 0 0 1px #f6c65414,0 0 #f6c65400}50%{box-shadow:inset 0 0 0 1px #fbd47638,0 0 18px #f6c65429}} \ No newline at end of file +html,body{background:radial-gradient(circle at 0 0,#29455d2e,#0000 30%),linear-gradient(#090e14f5,#0f161ffa);width:100%;height:100%;margin:0;padding:0;overflow:hidden}body{color:var(--text);font-family:var(--font)}.dispatch-shell{flex-direction:column;gap:14px;height:100%;padding:18px;display:flex}.dispatch-header{justify-content:space-between;align-items:center;gap:16px;display:flex}.dispatch-panel-actions{align-items:center;gap:8px;display:flex}.dispatch-kicker{color:var(--accent);text-transform:uppercase;letter-spacing:.1em;margin:0 0 4px;font-size:11px;font-weight:700}.dispatch-header h2{margin:0;font-size:24px;font-weight:650}.dispatch-header button,.dispatch-btn,.dispatch-select{color:var(--text);background:#181f28e6;border:1px solid #ffffff1f}.dispatch-header button,.dispatch-btn{cursor:pointer;padding:10px 14px}.dispatch-btn-compact{min-height:32px;padding:8px 10px;font-size:12px}.dispatch-btn-secondary{background:#352827eb}.dispatch-status{color:#e9f1f8c7;min-height:20px;font-size:13px}.dispatch-status[data-type=success]{color:#79d28a}.dispatch-status[data-type=error]{color:#ff8a80}.dispatch-danger-alert{color:#ffd4cf;letter-spacing:.06em;text-transform:uppercase;background:linear-gradient(90deg,#5c1212f0,#801d1dd1);border:1px solid #ff6b6b61;padding:10px 12px;font-size:12px;font-weight:700;animation:1.35s ease-in-out infinite cad-danger-pulse}.dispatch-danger-alert.is-hidden{display:none}.dispatch-warning-alert{color:#ffe9b2;letter-spacing:.06em;text-transform:uppercase;background:linear-gradient(90deg,#59400cf0,#7d5c12d6);border:1px solid #f6c6546b;padding:10px 12px;font-size:12px;font-weight:700;animation:1.35s ease-in-out infinite cad-warning-pulse}.dispatch-warning-alert.is-hidden{display:none}.dispatch-metrics{grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;display:grid}.metric-card{background:#0d131ab8;border:1px solid #ffffff14;padding:14px}.metric-label{color:#e9f1f899;text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;font-size:11px;display:block}.metric-card strong{font-size:28px;font-weight:700}.metric-card.is-danger{background:linear-gradient(#4a1111db,#160d10eb);border-color:#ff6b6b57;animation:1.35s ease-in-out infinite cad-danger-pulse;box-shadow:inset 0 0 0 1px #ff6b6b1f}.metric-card.is-warning{background:linear-gradient(#5c410edb,#1d160beb);border-color:#f6c65457;animation:1.35s ease-in-out infinite cad-warning-pulse;box-shadow:inset 0 0 0 1px #f6c6541f}.dispatch-grid{flex:1;grid-template-columns:repeat(12,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);gap:14px;min-height:0;display:grid}.dispatch-panel{background:#0b1118c7;border:1px solid #ffffff14;flex-direction:column;min-width:0;min-height:0;display:flex}.dispatch-panel-open{grid-column:span 5}.dispatch-panel-assigned{grid-column:span 7}.dispatch-panel-groups{grid-column:span 8}.dispatch-panel-activity{grid-column:span 4}.dispatch-panel-header{border-bottom:1px solid #ffffff14;justify-content:space-between;align-items:center;padding:12px 14px;display:flex}.dispatch-panel-header h3{text-transform:uppercase;letter-spacing:.08em;color:var(--accent);margin:0;font-size:13px}.dispatch-list{flex-direction:column;flex:1;gap:10px;padding:12px;display:flex;overflow:auto}.dispatch-inline-section{flex-direction:column;gap:10px;display:flex}.dispatch-inline-header{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;font-size:11px;font-weight:700}.dispatch-card{background:#131a22b8;border:1px solid #ffffff0f;padding:12px}.dispatch-card-interactive{cursor:pointer}.dispatch-card-interactive:hover{background:#171f28d1;border-color:#5bbbff33}.dispatch-card-header,.dispatch-meta{justify-content:space-between;gap:10px;display:flex}.dispatch-card-header-actions{align-items:center;gap:8px;display:flex}.dispatch-card-header-main{align-items:center;gap:8px;min-width:0;display:flex}.dispatch-card-header{margin-bottom:8px}.dispatch-description{color:#f1f6fbd1;margin:0 0 10px;font-size:13px;line-height:1.45}.dispatch-meta{color:#e5edf4b3;margin-bottom:10px;font-size:12px}.dispatch-badge{color:var(--accent);text-transform:uppercase;background:#102b3db3;border:1px solid #5bbbff2e;padding:3px 7px;font-size:11px}.dispatch-alert-badge{color:#ffd8d1;text-transform:uppercase;letter-spacing:.08em;background:#5f1717e0;border:1px solid #ff6b6b70;padding:3px 7px;font-size:11px;font-weight:700}.dispatch-icon-btn{width:32px;height:32px;color:var(--text);cursor:pointer;background:#181f28eb;border:1px solid #ffffff24;padding:0}.dispatch-icon-btn:hover{background:#202a34f5}.dispatch-actions{flex-direction:column;gap:8px;display:flex}.dispatch-card.is-danger{background:linear-gradient(#451416c7,#1c1115eb);border-color:#ff6b6b57;animation:1.35s ease-in-out infinite cad-danger-pulse;box-shadow:inset 0 0 0 1px #ff6b6b1a}.dispatch-card.is-danger .dispatch-meta,.dispatch-card.is-danger .dispatch-description{color:#ffe8e4d1}.dispatch-card.is-warning{background:linear-gradient(#564011c7,#221b10eb);border-color:#f6c65457;animation:1.35s ease-in-out infinite cad-warning-pulse;box-shadow:inset 0 0 0 1px #f6c6541a}.dispatch-card.is-warning .dispatch-meta,.dispatch-card.is-warning .dispatch-description{color:#fff3d6d6}.dispatch-actions-split{margin-top:10px}.dispatch-select{width:100%;padding:9px 10px}.dispatch-textarea{width:100%;min-height:92px;color:var(--text);font:inherit;resize:vertical;box-sizing:border-box;background:#181f28eb;border:1px solid #ffffff1f;padding:10px 12px}.placeholder-message{text-align:center;color:#e9f1f899;padding:18px}.dispatch-modal{z-index:30;box-sizing:border-box;justify-content:center;align-items:center;padding:32px 24px;display:flex;position:fixed;inset:0}.dispatch-modal.is-hidden{display:none}.dispatch-modal-backdrop{background:#04080cb8;position:absolute;inset:0}.dispatch-modal-dialog{background:#0b1118fa;border:1px solid #ffffff1f;flex-direction:column;width:min(560px,100% - 48px);max-height:calc(100vh - 64px);margin:0;display:flex;position:relative;box-shadow:0 24px 64px #0000006b}.dispatch-modal-header,.dispatch-modal-actions{justify-content:space-between;align-items:center;gap:12px;padding:14px 16px;display:flex}.dispatch-modal-header{border-bottom:1px solid #ffffff14}.dispatch-modal-header h3{margin:0;font-size:22px;font-weight:650}.dispatch-modal-body{flex:1;min-height:0;padding:16px;overflow:auto}.dispatch-meta-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-bottom:18px;display:grid}.dispatch-meta-grid strong{margin-top:4px;font-size:14px;font-weight:600;display:block}.dispatch-modal-fields{gap:12px;display:grid}.dispatch-field{gap:6px;display:grid}.dispatch-field span{text-transform:uppercase;letter-spacing:.08em;color:#e9f1f8b3;font-size:12px;font-weight:650}.dispatch-modal-actions{border-top:1px solid #ffffff14;justify-content:flex-end}.dispatch-detail-block,.dispatch-detail-list{background:#131a22b8;border:1px solid #ffffff14}.dispatch-detail-block{color:#f1f6fbd1;white-space:pre-wrap;padding:12px;line-height:1.45}.dispatch-detail-list{gap:1px;display:grid;overflow:hidden}.dispatch-detail-row{background:#0e141ceb;grid-template-columns:minmax(0,180px) minmax(0,1fr);gap:12px;padding:10px 12px;display:grid}.dispatch-detail-label{color:#e9f1f8a3;text-transform:uppercase;letter-spacing:.06em;font-size:12px;font-weight:650}.dispatch-detail-value{color:#f1f6fbd6;word-break:break-word;white-space:pre-wrap;line-height:1.4}@keyframes cad-danger-pulse{0%,to{box-shadow:inset 0 0 0 1px #ff6b6b14,0 0 #ff6b6b00}50%{box-shadow:inset 0 0 0 1px #ff8d8d38,0 0 18px #ff6b6b29}}@keyframes cad-warning-pulse{0%,to{box-shadow:inset 0 0 0 1px #f6c65414,0 0 #f6c65400}50%{box-shadow:inset 0 0 0 1px #fbd47638,0 0 18px #f6c65429}} \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-dispatcher.js b/arma/client/addons/cad/ui/_site/cad-dispatcher.js index d9c1de4..db9bd0d 100644 --- a/arma/client/addons/cad/ui/_site/cad-dispatcher.js +++ b/arma/client/addons/cad/ui/_site/cad-dispatcher.js @@ -1 +1 @@ -window.cadDispatcherFormatters={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(", ")}`},getSortedGroups(){return this.groups.slice().sort((e,t)=>{const s="danger"===(e.status||"")?0:1,n="danger"===(t.status||"")?0:1;if(s!==n)return s-n;const r=e.callsign||e.groupId||"",i=t.callsign||t.groupId||"";return r.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},getRequestTypeLabel(e){switch(e){case"medevac_9line":return"9-Line MEDEVAC";case"ace_lace":return"ACE/LACE";case"fire_support":return"Fire Support";case"air_support":return"Air Support";case"logreq":return"LOGREQ";default:return(e||"request").replaceAll("_"," ")}},buildGroupOptions(e){return this.getSortedGroups().map(t=>{const s=t.groupId||"";return``}).join("")},formatRequestFieldLabel:e=>(e||"field").replaceAll("_"," ").replace(/\b\w/g,e=>e.toUpperCase()),formatRequestFieldValue(e){if(Array.isArray(e))return e.join(", ");if(e&&"object"==typeof e)return JSON.stringify(e);return String(e??"").trim()||"Not provided"},buildRequestOrderNote(e){const t=this.getRequestTypeLabel(e.type||"request"),s=e.groupCallsign||e.groupId||"Unknown Group",n=(e.summary||"").trim(),r=e.fields&&"object"==typeof e.fields?Object.entries(e.fields).map(([e,t])=>{const s=this.formatRequestFieldValue(t);return"Not provided"===s?"":`${this.formatRequestFieldLabel(e)} ${s}`}).filter(Boolean):[],i=r.length?r:[n].filter(Boolean);return i.length?`${t} requested by ${s}. ${i.join(" | ")}`:`${t} requested by ${s}.`}},window.cadDispatcherModals={openOrderModal(){this.convertingRequestId="",this.populateOrderModal(),document.getElementById("dispatcherOrderModalTitle").textContent="Create Support Order",document.getElementById("dispatcherOrderModal").classList.remove("is-hidden")},closeOrderModal(){this.convertingRequestId="",document.getElementById("dispatcherOrderNoteInput").value="",document.getElementById("dispatcherOrderPrioritySelect").value="priority",document.getElementById("dispatcherOrderModalTitle").textContent="Create Support Order",document.getElementById("dispatcherOrderModal").classList.add("is-hidden")},openRequestModal(e){const t=this.requests.find(t=>t.requestId===e);t&&(this.viewingRequestId=e,this.populateRequestModal(t),document.getElementById("dispatcherRequestModal").classList.remove("is-hidden"))},closeRequestModal(){this.viewingRequestId="",document.getElementById("dispatcherRequestModal").classList.add("is-hidden")},syncRequestModal(){if(!this.viewingRequestId)return;const e=this.requests.find(e=>e.requestId===this.viewingRequestId);e?this.populateRequestModal(e):this.closeRequestModal()},populateRequestModal(e){const t=e.fields&&"object"==typeof e.fields?Object.entries(e.fields):[],s=t.length?t.map(([e,t])=>`\n
\n ${this.formatRequestFieldLabel(e)}\n ${this.formatRequestFieldValue(t)}\n
\n `).join(""):'

No submitted fields.

';document.getElementById("dispatcherRequestTitle").textContent=e.title||e.requestId||"Support Request",document.getElementById("dispatcherRequestPriority").textContent=(e.priority||"priority").replaceAll("_"," "),document.getElementById("dispatcherRequestGroup").textContent=e.groupCallsign||e.groupId||"Unknown",document.getElementById("dispatcherRequestType").textContent=this.getRequestTypeLabel(e.type||"request"),document.getElementById("dispatcherRequestSummary").textContent=e.summary||"No summary provided.",document.getElementById("dispatcherRequestFields").innerHTML=s},convertRequestToOrder(e){const t=this.requests.find(t=>(t.requestId||"")===e);if(!t)return void this.setStatus("Selected request is no longer available.","error");const s=t.groupId||"";if(!s)return void this.setStatus("Selected request has no owning group to target.","error");this.groups.find(e=>(e.groupId||"")===s)?(this.convertingRequestId=e,this.populateOrderModal({selectedAssigneeID:this.getSortedGroups().find(e=>(e.groupId||"")!==s)?.groupId||"",selectedTargetID:s,note:this.buildRequestOrderNote(t),priority:t.priority||"priority"}),document.getElementById("dispatcherOrderModalTitle").textContent="Create Order From Request",document.getElementById("dispatcherOrderModal").classList.remove("is-hidden"),this.setStatus("Preparing dispatch order from request...","info")):this.setStatus("Selected request group is no longer available.","error")},convertViewedRequestToOrder(){if(!this.viewingRequestId)return;const e=this.viewingRequestId;this.closeRequestModal(),this.convertRequestToOrder(e)},populateOrderModal(e={}){const t=this.getSortedGroups(),s=document.getElementById("dispatcherOrderAssigneeSelect"),n=document.getElementById("dispatcherOrderTargetSelect"),r=document.getElementById("dispatcherOrderNoteInput"),i=document.getElementById("dispatcherOrderPrioritySelect");if(!s||!n)return;const d=e.selectedAssigneeID||"",a=e.selectedTargetID||"",o=d||t.find(e=>(e.groupId||"")!==a)?.groupId||t[0]?.groupId||"",c=a||t.find(e=>(e.groupId||"")!==o)?.groupId||t[0]?.groupId||"";s.innerHTML=this.buildGroupOptions(o),n.innerHTML=this.buildGroupOptions(c),r&&(r.value=e.note||""),i&&(i.value=e.priority||"priority")},syncOrderModal(){const e=document.getElementById("dispatcherOrderModal");e&&!e.classList.contains("is-hidden")&&this.populateOrderModal({selectedAssigneeID:document.getElementById("dispatcherOrderAssigneeSelect")?.value||"",selectedTargetID:document.getElementById("dispatcherOrderTargetSelect")?.value||"",note:document.getElementById("dispatcherOrderNoteInput")?.value||"",priority:document.getElementById("dispatcherOrderPrioritySelect")?.value||"priority"})},openGroupModal(e){const t=this.groups.find(t=>t.groupId===e);t&&(this.editingGroupId=e,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",document.getElementById("dispatcherModalRoleSelect").innerHTML=this.roles.map(e=>``).join(""),document.getElementById("dispatcherModalStatusSelect").innerHTML=this.statuses.map(e=>``).join(""),document.getElementById("dispatcherGroupModal").classList.remove("is-hidden"))},closeGroupModal(){this.editingGroupId="",document.getElementById("dispatcherGroupModal").classList.add("is-hidden")},syncOpenModal(){if(!this.editingGroupId)return;const e=this.groups.find(e=>e.groupId===this.editingGroupId);e?(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"):this.closeGroupModal()}},window.cadDispatcherRender={updateDangerAlert(){const e=document.getElementById("dispatcherDangerAlert");if(!e)return;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("dispatcherRequestAlert");if(!e)return;const t=this.buildSupportAlertMessage();if(!t)return e.textContent="",void e.classList.add("is-hidden");e.textContent=t,e.classList.remove("is-hidden")},buildGroupEditorButton:e=>`\n \n ⚙\n \n `,buildCloseOrderButton:e=>`\n \n Close\n \n `,buildCloseRequestButton:e=>`\n \n Close\n \n `,buildConvertRequestButton:e=>`\n \n Convert to Order\n \n `,renderMetrics(){const e=this.contracts.filter(e=>"unassigned"!==(e.assignmentState||"unassigned")),t=this.contracts.filter(e=>"unassigned"===(e.assignmentState||"unassigned")),s=this.requests.length,n=this.getSupportAlertRequests(),r=this.groups.filter(e=>"danger"===(e.status||""));document.getElementById("metricOpenContracts").textContent=t.length,document.getElementById("metricAssignedContracts").textContent=e.length,document.getElementById("metricActiveGroups").textContent=this.groups.length,document.getElementById("metricOpenRequests").textContent=s,document.getElementById("metricDangerGroups").textContent=r.length;const i=document.getElementById("metricDangerGroupsCard");i&&i.classList.toggle("is-danger",r.length>0);const d=document.getElementById("metricOpenRequestsCard");d&&d.classList.toggle("is-warning",n.length>0)},renderOpenContracts(){const e=document.getElementById("dispatcherOpenContracts"),t=this.contracts.filter(e=>"unassigned"===(e.assignmentState||"unassigned"));if(!t.length)return void(e.innerHTML='

No open contracts.

');const s=this.buildGroupOptions("");e.innerHTML=t.map(e=>{const t=e.taskId||e.taskID||"",n=Array.isArray(e.position)?e.position:[0,0,0],r=this.groups.find(t=>t.groupId===(e.targetGroupId||""));return`\n
\n
\n ${e.title||t}\n ${this.formatTypeLabel(e)}\n
\n

${e.description||""}

\n
\n Unassigned\n ${window.mapUI.formatPosition(n)}\n
\n
\n Target: ${r?r.callsign:e.targetGroupCallsign||"None"}\n Priority: ${(e.priority||"priority").replaceAll("_"," ")}\n
\n
\n \n \n
\n
\n `}).join("")},renderAssignedContracts(){const e=document.getElementById("dispatcherAssignedContracts"),t=this.contracts.filter(e=>"unassigned"!==(e.assignmentState||"unassigned"));t.length?e.innerHTML=t.map(e=>{const t=e.taskId||e.taskID||"",s=this.groups.find(t=>t.groupId===(e.assignedGroupId||"")),n=this.groups.find(t=>t.groupId===(e.targetGroupId||"")),r=this.isDispatchOrder(e);return`\n
\n
\n ${e.title||t}\n ${e.assignmentState||"assigned"}\n
\n

${e.description||""}

\n
\n Group: ${s?s.callsign:e.assignedGroupId||"Unknown"}\n Type: ${this.formatTypeLabel(e)}\n
\n
\n Target: ${n?n.callsign:e.targetGroupCallsign||"None"}\n Priority: ${(e.priority||"priority").replaceAll("_"," ")}\n
\n ${r?`
${this.buildCloseOrderButton(t)}
`:""}\n
\n `}).join(""):e.innerHTML='

No assigned contracts.

'},renderGroups(){const e=document.getElementById("dispatcherGroups");this.groups.length?e.innerHTML=this.getSortedGroups().map(e=>{const t="danger"===(e.status||"");return`\n
\n
\n
\n ${e.callsign||e.groupId}\n ${e.role||"group"}\n ${t?'Danger':""}\n
\n
\n ${this.buildGroupEditorButton(e.groupId)}\n
\n
\n
\n Leader: ${e.leaderName||"Unknown"}\n Status: ${e.status||"unknown"}\n
\n
\n Org: ${e.orgId||"default"}\n Task: ${e.currentTaskId||"None"}\n
\n
\n `}).join(""):e.innerHTML='

No active groups available.

'},renderActivity(){const e=document.getElementById("dispatcherActivity"),t=this.requests.length?this.requests.map(e=>`\n
\n
\n ${e.title||e.requestId||"Support Request"}\n ${(e.priority||"priority").replaceAll("_"," ")}\n
\n

${e.summary||""}

\n
\n Group: ${e.groupCallsign||e.groupId||"Unknown"}\n ${this.getRequestTypeLabel(e.type||"request")}\n
\n
\n ${this.buildConvertRequestButton(e.requestId||"")}\n ${this.buildCloseRequestButton(e.requestId||"")}\n
\n
\n `).join(""):'

No active support requests.

',s=this.activity.length?this.activity.slice().reverse().slice(0,8).map(e=>`\n
\n
\n ${e.type||"activity"}\n ${Math.round(e.timestamp||0)}s\n
\n

${e.message||""}

\n
\n `).join(""):'

No recent activity.

';e.innerHTML=`\n
\n
Support Requests
\n ${t}\n
\n
\n
Recent Activity
\n ${s}\n
\n `},render(){this.updateDangerAlert(),this.updateRequestAlert(),this.renderMetrics(),this.renderOpenContracts(),this.renderAssignedContracts(),this.renderGroups(),this.renderActivity()}};const dispatcherFormatters=window.cadDispatcherFormatters||{},dispatcherModals=window.cadDispatcherModals||{},dispatcherRender=window.cadDispatcherRender||{};window.cadDispatcher={contracts:[],requests:[],groups:[],activity:[],session:{},editingGroupId:"",viewingRequestId:"",convertingRequestId:"",statuses:["available","en_route","on_task","holding","danger","unavailable"],roles:["infantry","recon","armor","air","logistics","support"],...dispatcherFormatters,...dispatcherModals,...dispatcherRender,init(){document.getElementById("dispatcherCreateOrderBtn").addEventListener("click",()=>{this.openOrderModal()}),document.getElementById("dispatcherGroupModalCloseBtn").addEventListener("click",()=>{this.closeGroupModal()}),document.getElementById("dispatcherGroupModalSaveBtn").addEventListener("click",()=>{this.applyGroupUpdates()}),document.querySelector("#dispatcherGroupModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeGroupModal()}),document.getElementById("dispatcherOrderModalCloseBtn").addEventListener("click",()=>{this.closeOrderModal()}),document.getElementById("dispatcherOrderModalSaveBtn").addEventListener("click",()=>{this.createDispatchOrder()}),document.querySelector("#dispatcherOrderModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeOrderModal()}),document.getElementById("dispatcherRequestModalCloseBtn").addEventListener("click",()=>{this.closeRequestModal()}),document.getElementById("dispatcherRequestModalDoneBtn").addEventListener("click",()=>{this.closeRequestModal()}),document.getElementById("dispatcherRequestConvertBtn").addEventListener("click",()=>{this.convertViewedRequestToOrder()}),document.querySelector("#dispatcherRequestModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeRequestModal()}),window.mapUI.sendEvent("cad::dispatcher::ready",{})},receiveHydrate(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:{};const t=document.getElementById("dispatcherStatusMessage");!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),this.syncOpenModal(),this.syncOrderModal(),this.syncRequestModal(),this.render()},setStatus(e,t){const s=document.getElementById("dispatcherStatusMessage");s&&(s.textContent=e||"",s.dataset.type=t||"")},createDispatchOrder(){const e=document.getElementById("dispatcherOrderAssigneeSelect").value,t=document.getElementById("dispatcherOrderTargetSelect").value,s=document.getElementById("dispatcherOrderPrioritySelect").value,n=document.getElementById("dispatcherOrderNoteInput").value,r=this.convertingRequestId&&this.requests.find(e=>(e.requestId||"")===this.convertingRequestId)||null;e&&t?e!==t?(this.setStatus(this.convertingRequestId?"Creating dispatch order from request...":"Creating dispatch order...","info"),window.mapUI.sendEvent("cad::dispatchOrder::create",{assigneeGroupID:e,targetGroupID:t,note:n.trim(),priority:s,request:r?{requestId:r.requestId||"",type:r.type||"",title:r.title||"",summary:r.summary||"",fields:r.fields&&"object"==typeof r.fields?r.fields:{}}:{}}),this.closeOrderModal()):this.setStatus("Assignee and target groups must be different.","error"):this.setStatus("Select both an assignee and a target group.","error")},assignTask(e){const t=document.getElementById(`dispatcher-assign-group-${e}`);t&&t.value?(this.setStatus("Submitting assignment...","info"),window.mapUI.sendEvent("cad::tasks::assign",{taskID:e,groupID:t.value,note:""})):this.setStatus("Select a group before assigning a contract.","error")},applyGroupUpdates(){if(!this.editingGroupId)return;const e=this.groups.find(e=>e.groupId===this.editingGroupId);if(!e)return void this.closeGroupModal();const t=document.getElementById("dispatcherModalRoleSelect").value,s=document.getElementById("dispatcherModalStatusSelect").value,n=t&&t!==(e.role||"")?t:"",r=s&&s!==(e.status||"")?s:"";if(!(n||r))return this.setStatus("No group changes to save.","info"),void this.closeGroupModal();this.setStatus("Updating group profile...","info"),window.mapUI.sendEvent("cad::groups::profile",{groupID:this.editingGroupId,role:n,status:r}),this.closeGroupModal()},closeDispatchOrder(e){e&&(this.setStatus("Closing dispatch order...","info"),window.mapUI.sendEvent("cad::dispatchOrder::close",{taskID:e}))},closeSupportRequest(e){e&&(this.setStatus("Closing support request...","info"),window.mapUI.sendEvent("cad::supportRequest::close",{requestID:e}))}},window.cadDispatcher.init(); \ No newline at end of file +window.cadDispatcherFormatters={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(", ")}`},getSortedGroups(){return this.groups.slice().sort((e,t)=>{const s="danger"===(e.status||"")?0:1,n="danger"===(t.status||"")?0:1;if(s!==n)return s-n;const r=e.callsign||e.groupId||"",a=t.callsign||t.groupId||"";return r.localeCompare(a)})},isDispatchOrder:e=>!!e.isDispatchOrder||"dispatch_order"===(e.type||""),formatTypeLabel(e){const t=(e.type||"task").replaceAll("_"," ");return this.isDispatchOrder(e)?"dispatch order":t},getRequestTypeLabel(e){switch(e){case"medevac_9line":return"9-Line MEDEVAC";case"ace_lace":return"ACE/LACE";case"fire_support":return"Fire Support";case"air_support":return"Air Support";case"logreq":return"LOGREQ";default:return(e||"request").replaceAll("_"," ")}},buildGroupOptions(e){return this.getSortedGroups().map(t=>{const s=t.groupId||"";return``}).join("")},buildTaskTypeOptions(e){return this.taskTypes.map(t=>{const s=t.value||"";return``}).join("")},formatRequestFieldLabel:e=>(e||"field").replaceAll("_"," ").replace(/\b\w/g,e=>e.toUpperCase()),formatRequestFieldValue(e){if(Array.isArray(e))return e.join(", ");if(e&&"object"==typeof e)return JSON.stringify(e);return String(e??"").trim()||"Not provided"},buildRequestOrderNote(e){const t=this.getRequestTypeLabel(e.type||"request"),s=e.groupCallsign||e.groupId||"Unknown Group",n=(e.summary||"").trim(),r=e.fields&&"object"==typeof e.fields?Object.entries(e.fields).map(([e,t])=>{const s=this.formatRequestFieldValue(t);return"Not provided"===s?"":`${this.formatRequestFieldLabel(e)} ${s}`}).filter(Boolean):[],a=r.length?r:[n].filter(Boolean);return a.length?`${t} requested by ${s}. ${a.join(" | ")}`:`${t} requested by ${s}.`}},window.cadDispatcherModals={openTaskModal(){this.populateTaskModal(),document.getElementById("dispatcherTaskModal").classList.remove("is-hidden")},closeTaskModal(){document.getElementById("dispatcherTaskModal").classList.add("is-hidden")},populateTaskModal(){const e=document.getElementById("dispatcherTaskTypeSelect");e&&(e.innerHTML=this.buildTaskTypeOptions(e.value||this.taskTypes[0]?.value||""))},openOrderModal(){this.convertingRequestId="",this.populateOrderModal(),document.getElementById("dispatcherOrderModalTitle").textContent="Create Support Order",document.getElementById("dispatcherOrderModal").classList.remove("is-hidden")},closeOrderModal(){this.convertingRequestId="",document.getElementById("dispatcherOrderNoteInput").value="",document.getElementById("dispatcherOrderPrioritySelect").value="priority",document.getElementById("dispatcherOrderModalTitle").textContent="Create Support Order",document.getElementById("dispatcherOrderModal").classList.add("is-hidden")},openRequestModal(e){const t=this.requests.find(t=>t.requestId===e);t&&(this.viewingRequestId=e,this.populateRequestModal(t),document.getElementById("dispatcherRequestModal").classList.remove("is-hidden"))},closeRequestModal(){this.viewingRequestId="",document.getElementById("dispatcherRequestModal").classList.add("is-hidden")},syncRequestModal(){if(!this.viewingRequestId)return;const e=this.requests.find(e=>e.requestId===this.viewingRequestId);e?this.populateRequestModal(e):this.closeRequestModal()},populateRequestModal(e){const t=e.fields&&"object"==typeof e.fields?Object.entries(e.fields):[],s=t.length?t.map(([e,t])=>`\n
\n ${this.formatRequestFieldLabel(e)}\n ${this.formatRequestFieldValue(t)}\n
\n `).join(""):'

No submitted fields.

';document.getElementById("dispatcherRequestTitle").textContent=e.title||e.requestId||"Support Request",document.getElementById("dispatcherRequestPriority").textContent=(e.priority||"priority").replaceAll("_"," "),document.getElementById("dispatcherRequestGroup").textContent=e.groupCallsign||e.groupId||"Unknown",document.getElementById("dispatcherRequestType").textContent=this.getRequestTypeLabel(e.type||"request"),document.getElementById("dispatcherRequestSummary").textContent=e.summary||"No summary provided.",document.getElementById("dispatcherRequestFields").innerHTML=s},convertRequestToOrder(e){const t=this.requests.find(t=>(t.requestId||"")===e);if(!t)return void this.setStatus("Selected request is no longer available.","error");const s=t.groupId||"";if(!s)return void this.setStatus("Selected request has no owning group to target.","error");this.groups.find(e=>(e.groupId||"")===s)?(this.convertingRequestId=e,this.populateOrderModal({selectedAssigneeID:this.getSortedGroups().find(e=>(e.groupId||"")!==s)?.groupId||"",selectedTargetID:s,note:this.buildRequestOrderNote(t),priority:t.priority||"priority"}),document.getElementById("dispatcherOrderModalTitle").textContent="Create Order From Request",document.getElementById("dispatcherOrderModal").classList.remove("is-hidden"),this.setStatus("Preparing dispatch order from request...","info")):this.setStatus("Selected request group is no longer available.","error")},convertViewedRequestToOrder(){if(!this.viewingRequestId)return;const e=this.viewingRequestId;this.closeRequestModal(),this.convertRequestToOrder(e)},populateOrderModal(e={}){const t=this.getSortedGroups(),s=document.getElementById("dispatcherOrderAssigneeSelect"),n=document.getElementById("dispatcherOrderTargetSelect"),r=document.getElementById("dispatcherOrderNoteInput"),a=document.getElementById("dispatcherOrderPrioritySelect");if(!s||!n)return;const d=e.selectedAssigneeID||"",i=e.selectedTargetID||"",o=d||t.find(e=>(e.groupId||"")!==i)?.groupId||t[0]?.groupId||"",c=i||t.find(e=>(e.groupId||"")!==o)?.groupId||t[0]?.groupId||"";s.innerHTML=this.buildGroupOptions(o),n.innerHTML=this.buildGroupOptions(c),r&&(r.value=e.note||""),a&&(a.value=e.priority||"priority")},syncOrderModal(){const e=document.getElementById("dispatcherOrderModal");e&&!e.classList.contains("is-hidden")&&this.populateOrderModal({selectedAssigneeID:document.getElementById("dispatcherOrderAssigneeSelect")?.value||"",selectedTargetID:document.getElementById("dispatcherOrderTargetSelect")?.value||"",note:document.getElementById("dispatcherOrderNoteInput")?.value||"",priority:document.getElementById("dispatcherOrderPrioritySelect")?.value||"priority"})},openGroupModal(e){const t=this.groups.find(t=>t.groupId===e);t&&(this.editingGroupId=e,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",document.getElementById("dispatcherModalRoleSelect").innerHTML=this.roles.map(e=>``).join(""),document.getElementById("dispatcherModalStatusSelect").innerHTML=this.statuses.map(e=>``).join(""),document.getElementById("dispatcherGroupModal").classList.remove("is-hidden"))},closeGroupModal(){this.editingGroupId="",document.getElementById("dispatcherGroupModal").classList.add("is-hidden")},syncOpenModal(){if(!this.editingGroupId)return;const e=this.groups.find(e=>e.groupId===this.editingGroupId);e?(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"):this.closeGroupModal()}},window.cadDispatcherRender={updateDangerAlert(){const e=document.getElementById("dispatcherDangerAlert");if(!e)return;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("dispatcherRequestAlert");if(!e)return;const t=this.buildSupportAlertMessage();if(!t)return e.textContent="",void e.classList.add("is-hidden");e.textContent=t,e.classList.remove("is-hidden")},buildGroupEditorButton:e=>`\n \n ⚙\n \n `,buildCloseOrderButton:e=>`\n \n Close\n \n `,buildCloseRequestButton:e=>`\n \n Close\n \n `,buildConvertRequestButton:e=>`\n \n Convert to Order\n \n `,renderMetrics(){const e=this.contracts.filter(e=>"unassigned"!==(e.assignmentState||"unassigned")),t=this.contracts.filter(e=>"unassigned"===(e.assignmentState||"unassigned")),s=this.requests.length,n=this.getSupportAlertRequests(),r=this.groups.filter(e=>"danger"===(e.status||""));document.getElementById("metricOpenContracts").textContent=t.length,document.getElementById("metricAssignedContracts").textContent=e.length,document.getElementById("metricActiveGroups").textContent=this.groups.length,document.getElementById("metricOpenRequests").textContent=s,document.getElementById("metricDangerGroups").textContent=r.length;const a=document.getElementById("metricDangerGroupsCard");a&&a.classList.toggle("is-danger",r.length>0);const d=document.getElementById("metricOpenRequestsCard");d&&d.classList.toggle("is-warning",n.length>0)},renderOpenContracts(){const e=document.getElementById("dispatcherOpenContracts"),t=this.contracts.filter(e=>"unassigned"===(e.assignmentState||"unassigned"));if(!t.length)return void(e.innerHTML='

No open contracts.

');const s=this.buildGroupOptions("");e.innerHTML=t.map(e=>{const t=e.taskId||e.taskID||"",n=Array.isArray(e.position)?e.position:[0,0,0],r=this.groups.find(t=>t.groupId===(e.targetGroupId||""));return`\n
\n
\n ${e.title||t}\n ${this.formatTypeLabel(e)}\n
\n

${e.description||""}

\n
\n Unassigned\n ${window.mapUI.formatPosition(n)}\n
\n
\n Target: ${r?r.callsign:e.targetGroupCallsign||"None"}\n Priority: ${(e.priority||"priority").replaceAll("_"," ")}\n
\n
\n \n \n
\n
\n `}).join("")},renderAssignedContracts(){const e=document.getElementById("dispatcherAssignedContracts"),t=this.contracts.filter(e=>"unassigned"!==(e.assignmentState||"unassigned"));t.length?e.innerHTML=t.map(e=>{const t=e.taskId||e.taskID||"",s=this.groups.find(t=>t.groupId===(e.assignedGroupId||"")),n=this.groups.find(t=>t.groupId===(e.targetGroupId||"")),r=this.isDispatchOrder(e);return`\n
\n
\n ${e.title||t}\n ${e.assignmentState||"assigned"}\n
\n

${e.description||""}

\n
\n Group: ${s?s.callsign:e.assignedGroupId||"Unknown"}\n Type: ${this.formatTypeLabel(e)}\n
\n
\n Target: ${n?n.callsign:e.targetGroupCallsign||"None"}\n Priority: ${(e.priority||"priority").replaceAll("_"," ")}\n
\n ${r?`
${this.buildCloseOrderButton(t)}
`:""}\n
\n `}).join(""):e.innerHTML='

No assigned contracts.

'},renderGroups(){const e=document.getElementById("dispatcherGroups");this.groups.length?e.innerHTML=this.getSortedGroups().map(e=>{const t="danger"===(e.status||"");return`\n
\n
\n
\n ${e.callsign||e.groupId}\n ${e.role||"group"}\n ${t?'Danger':""}\n
\n
\n ${this.buildGroupEditorButton(e.groupId)}\n
\n
\n
\n Leader: ${e.leaderName||"Unknown"}\n Status: ${e.status||"unknown"}\n
\n
\n Org: ${e.orgId||"default"}\n Task: ${e.currentTaskId||"None"}\n
\n
\n `}).join(""):e.innerHTML='

No active groups available.

'},renderActivity(){const e=document.getElementById("dispatcherActivity"),t=this.requests.length?this.requests.map(e=>`\n
\n
\n ${e.title||e.requestId||"Support Request"}\n ${(e.priority||"priority").replaceAll("_"," ")}\n
\n

${e.summary||""}

\n
\n Group: ${e.groupCallsign||e.groupId||"Unknown"}\n ${this.getRequestTypeLabel(e.type||"request")}\n
\n
\n ${this.buildConvertRequestButton(e.requestId||"")}\n ${this.buildCloseRequestButton(e.requestId||"")}\n
\n
\n `).join(""):'

No active support requests.

',s=this.activity.length?this.activity.slice().reverse().slice(0,8).map(e=>`\n
\n
\n ${e.type||"activity"}\n ${Math.round(e.timestamp||0)}s\n
\n

${e.message||""}

\n
\n `).join(""):'

No recent activity.

';e.innerHTML=`\n
\n
Support Requests
\n ${t}\n
\n
\n
Recent Activity
\n ${s}\n
\n `},render(){this.updateDangerAlert(),this.updateRequestAlert(),this.renderMetrics(),this.renderOpenContracts(),this.renderAssignedContracts(),this.renderGroups(),this.renderActivity()}};const dispatcherFormatters=window.cadDispatcherFormatters||{},dispatcherModals=window.cadDispatcherModals||{},dispatcherRender=window.cadDispatcherRender||{};window.cadDispatcher={contracts:[],requests:[],groups:[],activity:[],session:{},editingGroupId:"",viewingRequestId:"",convertingRequestId:"",taskTypes:[{value:"attack",label:"Attack"},{value:"defend",label:"Defend"},{value:"delivery",label:"Delivery"},{value:"destroy",label:"Destroy"},{value:"defuse",label:"Defuse"},{value:"hostage",label:"Hostage"},{value:"hvtkill",label:"Kill HVT"},{value:"hvtcapture",label:"Capture HVT"}],statuses:["available","en_route","on_task","holding","danger","unavailable"],roles:["infantry","recon","armor","air","logistics","support"],...dispatcherFormatters,...dispatcherModals,...dispatcherRender,init(){document.getElementById("dispatcherRequestTaskBtn").addEventListener("click",()=>{this.openTaskModal()}),document.getElementById("dispatcherCreateOrderBtn").addEventListener("click",()=>{this.openOrderModal()}),document.getElementById("dispatcherGroupModalCloseBtn").addEventListener("click",()=>{this.closeGroupModal()}),document.getElementById("dispatcherGroupModalSaveBtn").addEventListener("click",()=>{this.applyGroupUpdates()}),document.querySelector("#dispatcherGroupModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeGroupModal()}),document.getElementById("dispatcherOrderModalCloseBtn").addEventListener("click",()=>{this.closeOrderModal()}),document.getElementById("dispatcherOrderModalSaveBtn").addEventListener("click",()=>{this.createDispatchOrder()}),document.querySelector("#dispatcherOrderModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeOrderModal()}),document.getElementById("dispatcherTaskModalCloseBtn").addEventListener("click",()=>{this.closeTaskModal()}),document.getElementById("dispatcherTaskModalSaveBtn").addEventListener("click",()=>{this.requestGeneratedTask()}),document.querySelector("#dispatcherTaskModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeTaskModal()}),document.getElementById("dispatcherRequestModalCloseBtn").addEventListener("click",()=>{this.closeRequestModal()}),document.getElementById("dispatcherRequestModalDoneBtn").addEventListener("click",()=>{this.closeRequestModal()}),document.getElementById("dispatcherRequestConvertBtn").addEventListener("click",()=>{this.convertViewedRequestToOrder()}),document.querySelector("#dispatcherRequestModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeRequestModal()}),window.mapUI.sendEvent("cad::dispatcher::ready",{})},receiveHydrate(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:{};const t=document.getElementById("dispatcherStatusMessage");!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),this.syncOpenModal(),this.syncOrderModal(),this.syncRequestModal(),this.render()},setStatus(e,t){const s=document.getElementById("dispatcherStatusMessage");s&&(s.textContent=e||"",s.dataset.type=t||"")},createDispatchOrder(){const e=document.getElementById("dispatcherOrderAssigneeSelect").value,t=document.getElementById("dispatcherOrderTargetSelect").value,s=document.getElementById("dispatcherOrderPrioritySelect").value,n=document.getElementById("dispatcherOrderNoteInput").value,r=this.convertingRequestId&&this.requests.find(e=>(e.requestId||"")===this.convertingRequestId)||null;e&&t?e!==t?(this.setStatus(this.convertingRequestId?"Creating dispatch order from request...":"Creating dispatch order...","info"),window.mapUI.sendEvent("cad::dispatchOrder::create",{assigneeGroupID:e,targetGroupID:t,note:n.trim(),priority:s,request:r?{requestId:r.requestId||"",type:r.type||"",title:r.title||"",summary:r.summary||"",fields:r.fields&&"object"==typeof r.fields?r.fields:{}}:{}}),this.closeOrderModal()):this.setStatus("Assignee and target groups must be different.","error"):this.setStatus("Select both an assignee and a target group.","error")},requestGeneratedTask(){const e=document.getElementById("dispatcherTaskTypeSelect").value;e?(this.setStatus("Requesting generated task...","info"),window.mapUI.sendEvent("cad::generatedTask::request",{taskType:e}),this.closeTaskModal()):this.setStatus("Select a task type before requesting a task.","error")},assignTask(e){const t=document.getElementById(`dispatcher-assign-group-${e}`);t&&t.value?(this.setStatus("Submitting assignment...","info"),window.mapUI.sendEvent("cad::tasks::assign",{taskID:e,groupID:t.value,note:""})):this.setStatus("Select a group before assigning a contract.","error")},applyGroupUpdates(){if(!this.editingGroupId)return;const e=this.groups.find(e=>e.groupId===this.editingGroupId);if(!e)return void this.closeGroupModal();const t=document.getElementById("dispatcherModalRoleSelect").value,s=document.getElementById("dispatcherModalStatusSelect").value,n=t&&t!==(e.role||"")?t:"",r=s&&s!==(e.status||"")?s:"";if(!(n||r))return this.setStatus("No group changes to save.","info"),void this.closeGroupModal();this.setStatus("Updating group profile...","info"),window.mapUI.sendEvent("cad::groups::profile",{groupID:this.editingGroupId,role:n,status:r}),this.closeGroupModal()},closeDispatchOrder(e){e&&(this.setStatus("Closing dispatch order...","info"),window.mapUI.sendEvent("cad::dispatchOrder::close",{taskID:e}))},closeSupportRequest(e){e&&(this.setStatus("Closing support request...","info"),window.mapUI.sendEvent("cad::supportRequest::close",{requestID:e}))}},window.cadDispatcher.init(); \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/dispatcher.html b/arma/client/addons/cad/ui/_site/dispatcher.html index a3b7124..1056721 100644 --- a/arma/client/addons/cad/ui/_site/dispatcher.html +++ b/arma/client/addons/cad/ui/_site/dispatcher.html @@ -1 +1 @@ -

Dispatch Dashboard

Operational Board

Open Contracts 0
Assigned Contracts 0
Active Groups 0
Open Requests 0
Groups In Danger 0

Available Contracts

Assigned Contracts

Group Board

Requests & Activity

\ No newline at end of file +

Dispatch Dashboard

Operational Board

Open Contracts 0
Assigned Contracts 0
Active Groups 0
Open Requests 0
Groups In Danger 0

Available Contracts

Assigned Contracts

Group Board

Requests & Activity

\ No newline at end of file diff --git a/arma/client/addons/cad/ui/src/dispatcher.html b/arma/client/addons/cad/ui/src/dispatcher.html index bfce3dd..b368315 100644 --- a/arma/client/addons/cad/ui/src/dispatcher.html +++ b/arma/client/addons/cad/ui/src/dispatcher.html @@ -49,15 +49,24 @@

Available Contracts

- +
+ + +
+ +