diff --git a/arma/client/addons/cad/XEH_postInitClient.sqf b/arma/client/addons/cad/XEH_postInitClient.sqf index dd44a7a..a64fb15 100644 --- a/arma/client/addons/cad/XEH_postInitClient.sqf +++ b/arma/client/addons/cad/XEH_postInitClient.sqf @@ -24,3 +24,17 @@ if (isNil QGVAR(CADUIBridge)) then { call FUNC(initUIBridge); }; GVAR(CADUIBridge) call ["handleGroupUpdateResponse", [_result]]; }] call CFUNC(addEventHandler); + +[QGVAR(responseCadRequest), { + params [["_result", createHashMap, [createHashMap]]]; + + GVAR(CADUIBridge) call ["handleRequestResponse", [_result]]; +}] call CFUNC(addEventHandler); + +[QGVAR(invalidateCadState), { + if (isNil QGVAR(CADRepository)) exitWith {}; + if !(GVAR(CADRepository) getOrDefault ["isOpen", false]) exitWith {}; + if (isNil QGVAR(CADUIBridge)) exitWith {}; + + GVAR(CADUIBridge) call ["requestHydrate", []]; +}] call CFUNC(addEventHandler); diff --git a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf index fa8a2b0..af2abdf 100644 --- a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf @@ -72,6 +72,48 @@ switch (_event) do { GVAR(CADUIBridge) call ["requestAssignTask", [_taskID, _groupID, _note]]; }; + case "cad::dispatchOrder::create": { + private _assigneeGroupID = ""; + private _targetGroupID = ""; + private _note = ""; + private _priority = "priority"; + if (_data isEqualType createHashMap) then { + _assigneeGroupID = _data getOrDefault ["assigneeGroupID", ""]; + _targetGroupID = _data getOrDefault ["targetGroupID", ""]; + _note = _data getOrDefault ["note", ""]; + _priority = _data getOrDefault ["priority", "priority"]; + }; + + GVAR(CADUIBridge) call ["requestCreateDispatchOrder", [_assigneeGroupID, _targetGroupID, _note, _priority]]; + }; + case "cad::supportRequest::submit": { + private _type = ""; + private _fields = createHashMap; + private _priority = "priority"; + if (_data isEqualType createHashMap) then { + _type = _data getOrDefault ["type", ""]; + _fields = _data getOrDefault ["fields", createHashMap]; + _priority = _data getOrDefault ["priority", "priority"]; + }; + + GVAR(CADUIBridge) call ["requestSubmitSupportRequest", [_type, _fields, _priority]]; + }; + case "cad::dispatchOrder::close": { + private _taskID = ""; + if (_data isEqualType createHashMap) then { + _taskID = _data getOrDefault ["taskID", ""]; + }; + + GVAR(CADUIBridge) call ["requestCloseDispatchOrder", [_taskID]]; + }; + case "cad::supportRequest::close": { + private _requestID = ""; + if (_data isEqualType createHashMap) then { + _requestID = _data getOrDefault ["requestID", ""]; + }; + + GVAR(CADUIBridge) call ["requestCloseSupportRequest", [_requestID]]; + }; case "cad::tasks::acknowledge": { private _taskID = ""; if (_data isEqualType createHashMap) then { @@ -108,6 +150,42 @@ switch (_event) do { GVAR(CADUIBridge) call ["requestGroupRole", [_groupID, _role]]; }; + case "cad::groups::profile": { + private _groupID = ""; + private _status = ""; + private _role = ""; + if (_data isEqualType createHashMap) then { + _groupID = _data getOrDefault ["groupID", ""]; + _status = _data getOrDefault ["status", ""]; + _role = _data getOrDefault ["role", ""]; + }; + + GVAR(CADUIBridge) call ["requestGroupProfile", [_groupID, _status, _role]]; + }; + case "cad::groups::focus": { + private _groupID = ""; + if (_data isEqualType createHashMap) then { + _groupID = _data getOrDefault ["groupID", ""]; + }; + + GVAR(CADUIBridge) call ["focusGroup", [_groupID]]; + }; + case "cad::tasks::focus": { + private _taskID = ""; + if (_data isEqualType createHashMap) then { + _taskID = _data getOrDefault ["taskID", ""]; + }; + + GVAR(CADUIBridge) call ["focusTask", [_taskID]]; + }; + case "cad::requests::focus": { + private _requestID = ""; + if (_data isEqualType createHashMap) then { + _requestID = _data getOrDefault ["requestID", ""]; + }; + + GVAR(CADUIBridge) call ["focusRequest", [_requestID]]; + }; case "map::zoomIn": { private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull]; if (isNull _mapCtrl) exitWith {}; diff --git a/arma/client/addons/cad/functions/fnc_initRepository.sqf b/arma/client/addons/cad/functions/fnc_initRepository.sqf index 28a44ba..07e535d 100644 --- a/arma/client/addons/cad/functions/fnc_initRepository.sqf +++ b/arma/client/addons/cad/functions/fnc_initRepository.sqf @@ -27,6 +27,7 @@ GVAR(CADRepository) = createHashMapObject [[ _self set ["isOpen", false]; _self set ["groups", []]; _self set ["contracts", []]; + _self set ["requests", []]; _self set ["assignments", []]; _self set ["activity", []]; _self set ["session", createHashMap]; @@ -37,6 +38,7 @@ GVAR(CADRepository) = createHashMapObject [[ createHashMapFromArray [ ["groups", +(_self getOrDefault ["groups", []])], ["contracts", +(_self getOrDefault ["contracts", []])], + ["requests", +(_self getOrDefault ["requests", []])], ["assignments", +(_self getOrDefault ["assignments", []])], ["activity", +(_self getOrDefault ["activity", []])], ["session", +(_self getOrDefault ["session", createHashMap])], @@ -67,6 +69,7 @@ GVAR(CADRepository) = createHashMapObject [[ _self set ["groups", +(_payload getOrDefault ["groups", []])]; _self set ["contracts", +(_payload getOrDefault ["contracts", []])]; + _self set ["requests", +(_payload getOrDefault ["requests", []])]; _self set ["assignments", +(_payload getOrDefault ["assignments", []])]; _self set ["activity", +(_payload getOrDefault ["activity", []])]; _self set ["session", +(_payload getOrDefault ["session", createHashMap])]; diff --git a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf index 2ce505b..0394bb5 100644 --- a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf @@ -213,6 +213,47 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ [SRPC(cad,requestAssignCadTask), [getPlayerUID player, _taskID, _groupID, _note]] call CFUNC(serverEvent); true }], + ["requestCreateDispatchOrder", compileFinal { + params [ + ["_assigneeGroupID", "", [""]], + ["_targetGroupID", "", [""]], + ["_note", "", [""]], + ["_priority", "priority", [""]] + ]; + + if (_assigneeGroupID isEqualTo "" || { _targetGroupID isEqualTo "" }) exitWith { false }; + + [SRPC(cad,requestCreateCadDispatchOrder), [getPlayerUID player, _assigneeGroupID, _targetGroupID, _note, _priority]] call CFUNC(serverEvent); + true + }], + ["requestSubmitSupportRequest", compileFinal { + params [ + ["_type", "", [""]], + ["_fields", createHashMap, [createHashMap]], + ["_priority", "priority", [""]] + ]; + + if (_type isEqualTo "") exitWith { false }; + + [SRPC(cad,requestSubmitCadSupportRequest), [getPlayerUID player, _type, _fields, _priority]] call CFUNC(serverEvent); + true + }], + ["requestCloseDispatchOrder", compileFinal { + params [["_taskID", "", [""]]]; + + if (_taskID isEqualTo "") exitWith { false }; + + [SRPC(cad,requestCloseCadDispatchOrder), [getPlayerUID player, _taskID]] call CFUNC(serverEvent); + true + }], + ["requestCloseSupportRequest", compileFinal { + params [["_requestID", "", [""]]]; + + if (_requestID isEqualTo "") exitWith { false }; + + [SRPC(cad,requestCloseCadSupportRequest), [getPlayerUID player, _requestID]] call CFUNC(serverEvent); + true + }], ["requestAcknowledgeTask", compileFinal { params [["_taskID", "", [""]]]; @@ -245,6 +286,87 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ [SRPC(cad,requestUpdateCadGroupRole), [getPlayerUID player, _groupID, _role]] call CFUNC(serverEvent); true }], + ["requestGroupProfile", compileFinal { + params [["_groupID", "", [""]], ["_status", "", [""]], ["_role", "", [""]]]; + + if (_groupID isEqualTo "") exitWith { false }; + if (_status isEqualTo "" && { _role isEqualTo "" }) exitWith { false }; + + [SRPC(cad,requestUpdateCadGroupProfile), [getPlayerUID player, _groupID, _status, _role]] call CFUNC(serverEvent); + true + }], + ["focusGroup", compileFinal { + params [["_groupID", "", [""]]]; + + if (_groupID isEqualTo "") exitWith { false }; + if (isNil QGVAR(CADRepository)) exitWith { false }; + + private _groups = GVAR(CADRepository) getOrDefault ["groups", []]; + private _groupIndex = _groups findIf { (_x getOrDefault ["groupId", ""]) isEqualTo _groupID }; + if (_groupIndex < 0) exitWith { false }; + + private _group = _groups # _groupIndex; + private _position = _group getOrDefault ["position", []]; + if !(_position isEqualType []) exitWith { false }; + if ((count _position) < 2) exitWith { false }; + + private _mapCtrl = _self call ["getMapControl", []]; + if (isNull _mapCtrl) exitWith { false }; + + private _targetPosition = [_position # 0, _position # 1, 0]; + _mapCtrl ctrlMapAnimAdd [0.35, ctrlMapScale _mapCtrl, _targetPosition]; + ctrlMapAnimCommit _mapCtrl; + true + }], + ["focusTask", compileFinal { + params [["_taskID", "", [""]]]; + + if (_taskID isEqualTo "") exitWith { false }; + if (isNil QGVAR(CADRepository)) exitWith { false }; + + private _contracts = GVAR(CADRepository) getOrDefault ["contracts", []]; + private _taskIndex = _contracts findIf { + private _entryTaskID = _x getOrDefault ["taskId", _x getOrDefault ["taskID", ""]]; + _entryTaskID isEqualTo _taskID + }; + if (_taskIndex < 0) exitWith { false }; + + private _task = _contracts # _taskIndex; + private _position = _task getOrDefault ["position", []]; + if !(_position isEqualType []) exitWith { false }; + if ((count _position) < 2) exitWith { false }; + + private _mapCtrl = _self call ["getMapControl", []]; + if (isNull _mapCtrl) exitWith { false }; + + private _targetPosition = [_position # 0, _position # 1, 0]; + _mapCtrl ctrlMapAnimAdd [0.35, ctrlMapScale _mapCtrl, _targetPosition]; + ctrlMapAnimCommit _mapCtrl; + true + }], + ["focusRequest", compileFinal { + params [["_requestID", "", [""]]]; + + if (_requestID isEqualTo "") exitWith { false }; + if (isNil QGVAR(CADRepository)) exitWith { false }; + + private _requests = GVAR(CADRepository) getOrDefault ["requests", []]; + private _requestIndex = _requests findIf { (_x getOrDefault ["requestId", ""]) isEqualTo _requestID }; + if (_requestIndex < 0) exitWith { false }; + + private _request = _requests # _requestIndex; + private _position = _request getOrDefault ["position", []]; + if !(_position isEqualType []) exitWith { false }; + if ((count _position) < 2) exitWith { false }; + + private _mapCtrl = _self call ["getMapControl", []]; + if (isNull _mapCtrl) exitWith { false }; + + private _targetPosition = [_position # 0, _position # 1, 0]; + _mapCtrl ctrlMapAnimAdd [0.35, ctrlMapScale _mapCtrl, _targetPosition]; + ctrlMapAnimCommit _mapCtrl; + true + }], ["refreshHydrate", compileFinal { if (isNil QGVAR(CADRepository)) exitWith { false }; GVAR(CADRepository) call ["pushHydratePayload", [_self]] @@ -301,6 +423,25 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ ["message", _result getOrDefault ["message", "Group update processed."]], ["success", _result getOrDefault ["success", false]] ]]] + }], + ["handleRequestResponse", compileFinal { + params [["_result", createHashMap, [createHashMap]]]; + + if (_self getOrDefault ["dispatcherReady", false]) then { + private _dispatcherCtrl = _self call ["getDispatcherControl", []]; + if !(isNull _dispatcherCtrl) then { + _dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [ + "window.cadDispatcher && window.cadDispatcher.setStatus(%1, %2);", + str (_result getOrDefault ["message", "Request processed."]), + str (["error", "success"] select (_result getOrDefault ["success", false])) + ]]; + }; + }; + + _self call ["sendEvent", ["cad::request::response", createHashMapFromArray [ + ["message", _result getOrDefault ["message", "Request processed."]], + ["success", _result getOrDefault ["success", false]] + ]]] }] ]; diff --git a/arma/client/addons/cad/ui/RscMapUI.hpp b/arma/client/addons/cad/ui/RscMapUI.hpp index b323f68..f4bffd3 100644 --- a/arma/client/addons/cad/ui/RscMapUI.hpp +++ b/arma/client/addons/cad/ui/RscMapUI.hpp @@ -89,9 +89,9 @@ class RscMapUI { class SidePanelBrowser: RscText { type = 106; idc = 1005; - x = "safeZoneX + (safeZoneW * 0.1) + (safeZoneW * 0.8) - 0.4630"; // Right edge of 80% box minus panel width + x = "safeZoneX + (safeZoneW * 0.1) + (safeZoneW * 0.8) - 0.5550"; // Right edge of 80% box minus panel width y = "safeZoneY + (safeZoneH * 0.1) + 0.10372"; // Below visible top bar - w = "0.4630"; // ~250px width + w = "0.5550"; // Wider panel for four-tab operations layout h = "(safeZoneH * 0.8) - 0.10372 - 0.0556"; // Full height minus visible bars colorBackground[] = {0, 0, 0, 0}; }; diff --git a/arma/client/addons/cad/ui/_site/cad-dispatcher.css b/arma/client/addons/cad/ui/_site/cad-dispatcher.css index e658608..c65da95 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-metrics{grid-template-columns:repeat(4,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}.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-card{background:#131a22b8;border:1px solid #ffffff0f;padding:12px}.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-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-actions-split{margin-top:10px}.dispatch-select{width:100%;padding:9px 10px}.placeholder-message{text-align:center;color:#e9f1f899;padding:18px}.dispatch-modal{z-index:30;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;width:min(480px,100% - 48px);margin:72px auto 0;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{padding:16px}.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} \ 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-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 diff --git a/arma/client/addons/cad/ui/_site/cad-dispatcher.js b/arma/client/addons/cad/ui/_site/cad-dispatcher.js index 4a471c8..6994fd7 100644 --- a/arma/client/addons/cad/ui/_site/cad-dispatcher.js +++ b/arma/client/addons/cad/ui/_site/cad-dispatcher.js @@ -1 +1 @@ -window.cadDispatcher={contracts:[],groups:[],activity:[],session:{},editingGroupId:"",statuses:["available","en_route","on_task","holding","danger","unavailable"],roles:["infantry","recon","armor","air","logistics","support"],init(){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=>``).join(""),document.getElementById("dispatcherModalStatusSelect").innerHTML=this.statuses.map(t=>``).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 \n ⚙\n \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='

No open contracts.

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

${t.description||""}

\n
\n Unassigned\n X: ${Math.round(n[0]||0)} Y: ${Math.round(n[1]||0)}\n
\n
\n \n \n
\n
\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
\n
\n ${t.title||e}\n ${t.assignmentState||"assigned"}\n
\n

${t.description||""}

\n
\n Group: ${s?s.callsign:t.assignedGroupId||"Unknown"}\n Type: ${t.type||"task"}\n
\n
\n `}).join(""):t.innerHTML='

No assigned contracts.

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

No active groups available.

'},renderActivity(){const t=document.getElementById("dispatcherActivity");this.activity.length?t.innerHTML=this.activity.slice().reverse().slice(0,12).map(t=>`\n
\n
\n ${t.type||"activity"}\n ${Math.round(t.timestamp||0)}s\n
\n

${t.message||""}

\n
\n `).join(""):t.innerHTML='

No recent activity.

'},render(){this.renderMetrics(),this.renderOpenContracts(),this.renderAssignedContracts(),this.renderGroups(),this.renderActivity()}},window.cadDispatcher.init(); \ No newline at end of file +window.cadDispatcher={contracts:[],requests:[],groups:[],activity:[],session:{},editingGroupId:"",viewingRequestId:"",statuses:["available","en_route","on_task","holding","danger","unavailable"],roles:["infantry","recon","armor","air","logistics","support"],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.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||"")},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("")},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")},openOrderModal(){this.populateOrderModal(),document.getElementById("dispatcherOrderModal").classList.remove("is-hidden")},closeOrderModal(){document.getElementById("dispatcherOrderNoteInput").value="",document.getElementById("dispatcherOrderPrioritySelect").value="priority",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()},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"},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},populateOrderModal(e,t){const s=this.getSortedGroups(),n=document.getElementById("dispatcherOrderAssigneeSelect"),r=document.getElementById("dispatcherOrderTargetSelect");if(!n||!r)return;const a=e||s[0]?.groupId||"",i=t||s.find(e=>(e.groupId||"")!==a)?.groupId||s[0]?.groupId||"";n.innerHTML=this.buildGroupOptions(a),r.innerHTML=this.buildGroupOptions(i)},syncOrderModal(){const e=document.getElementById("dispatcherOrderModal");e&&!e.classList.contains("is-hidden")&&this.populateOrderModal(document.getElementById("dispatcherOrderAssigneeSelect")?.value||"",document.getElementById("dispatcherOrderTargetSelect")?.value||"")},createDispatchOrder(){const e=document.getElementById("dispatcherOrderAssigneeSelect").value,t=document.getElementById("dispatcherOrderTargetSelect").value,s=document.getElementById("dispatcherOrderPrioritySelect").value,n=document.getElementById("dispatcherOrderNoteInput").value;e&&t?e!==t?(this.setStatus("Creating dispatch order...","info"),window.mapUI.sendEvent("cad::dispatchOrder::create",{assigneeGroupID:e,targetGroupID:t,note:n.trim(),priority:s}),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")},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()},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}))},buildGroupEditorButton:e=>`\n \n ⚙\n \n `,buildCloseOrderButton:e=>`\n \n Close\n \n `,buildCloseRequestButton:e=>`\n \n Close\n \n `,closeSupportRequest(e){e&&(this.setStatus("Closing support request...","info"),window.mapUI.sendEvent("cad::supportRequest::close",{requestID:e}))},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 i=document.getElementById("metricOpenRequestsCard");i&&i.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.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()}},window.cadDispatcher.init(); \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-shared.js b/arma/client/addons/cad/ui/_site/cad-shared.js index 75008e5..7032729 100644 --- a/arma/client/addons/cad/ui/_site/cad-shared.js +++ b/arma/client/addons/cad/ui/_site/cad-shared.js @@ -1 +1 @@ -window.mapUIState={layersPanelVisible:!0,sidePanelElement:null},window.mapUI={sendEvent(e,t){A3API.SendAlert(JSON.stringify({event:e,data:t}))},updateCoordinates(e,t){const n=document.getElementById("coordsDisplay");n&&(n.textContent=`X: ${Math.round(e).toString().padStart(4,"0")} Y: ${Math.round(t).toString().padStart(4,"0")}`)},updateScale(e){const t=document.getElementById("scaleDisplay");t&&(t.textContent=`Scale: 1:${Math.round(e)}`)},updateStatus(e){const t=document.getElementById("statusText");t&&(t.textContent=e)}},window.updateCoordinates=window.mapUI.updateCoordinates,window.updateScale=window.mapUI.updateScale,window.updateStatus=window.mapUI.updateStatus,window.ForgeBridge=window.ForgeBridge||{_handlers:{},on(e,t){this._handlers[e]=this._handlers[e]||[],this._handlers[e].push(t)},ready:e=>(window.mapUI.sendEvent("cad::ready",e||{}),!0),receive(e){if(!e||"object"!=typeof e)return;(this._handlers[e.event]||[]).forEach(t=>t(e.data||{}))},send:(e,t)=>(window.mapUI.sendEvent(e,t||{}),!0),close:e=>(window.mapUI.sendEvent("map::close",e||{}),!0)}; \ No newline at end of file +window.mapUIState={layersPanelVisible:!0,sidePanelElement:null},window.mapUI={formatGridCoordinate:t=>Math.round(Number(t)||0).toString().padStart(4,"0"),formatPosition(t){const e=Array.isArray(t)?t:[0,0,0];return`X: ${this.formatGridCoordinate(e[0])} Y: ${this.formatGridCoordinate(e[1])}`},sendEvent(t,e){A3API.SendAlert(JSON.stringify({event:t,data:e}))},updateCoordinates(t,e){const n=document.getElementById("coordsDisplay");n&&(n.textContent=this.formatPosition([t,e,0]))},updateScale(t){const e=document.getElementById("scaleDisplay");e&&(e.textContent=`Scale: 1:${Math.round(t)}`)},updateStatus(t){const e=document.getElementById("statusText");e&&(e.textContent=t)}},window.updateCoordinates=window.mapUI.updateCoordinates,window.updateScale=window.mapUI.updateScale,window.updateStatus=window.mapUI.updateStatus,window.ForgeBridge=window.ForgeBridge||{_handlers:{},on(t,e){this._handlers[t]=this._handlers[t]||[],this._handlers[t].push(e)},ready:t=>(window.mapUI.sendEvent("cad::ready",t||{}),!0),receive(t){if(!t||"object"!=typeof t)return;(this._handlers[t.event]||[]).forEach(e=>e(t.data||{}))},send:(t,e)=>(window.mapUI.sendEvent(t,e||{}),!0),close:t=>(window.mapUI.sendEvent("map::close",t||{}),!0)}; \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-sidepanel.css b/arma/client/addons/cad/ui/_site/cad-sidepanel.css index d58ae49..7f3b6d0 100644 --- a/arma/client/addons/cad/ui/_site/cad-sidepanel.css +++ b/arma/client/addons/cad/ui/_site/cad-sidepanel.css @@ -1 +1 @@ -html,body{background:var(--panel);border-left:1px solid var(--stroke);width:100%;height:100%;box-shadow:var(--shadow);-webkit-backdrop-filter:blur(12px);margin:0;padding:0;overflow:hidden}body{opacity:1;visibility:visible}.panel-header{border-bottom:1px solid var(--stroke);background:linear-gradient(#ffffff0d,#0000);justify-content:space-between;align-items:center;padding:14px;display:flex}.panel-header h3{color:var(--accent);text-transform:uppercase;letter-spacing:.8px;font-size:14px;font-weight:650}.panel-content{height:calc(100% - 56px);padding:14px;overflow:auto}.placeholder-message{text-align:center;padding:20px}.placeholder-message p{color:var(--muted);font-size:13px;font-style:italic}.cad-tabs{grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:12px;display:grid}.cad-tab{color:#f3f6f9c7;text-transform:uppercase;letter-spacing:.08em;cursor:pointer;background:#141b21e0;border:1px solid #ffffff24;padding:8px 10px;font-size:11px}.cad-tab:hover{color:#f3f6f9;background:#1f282ff0}.cad-tab.is-active{color:var(--accent);background:#0f283af5;border-color:#5bbbff6b}.cad-tab-panels{min-height:0}.cad-section{display:none}.cad-section.is-active{display:block}.cad-section-header{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;font-size:12px;font-weight:700}.task-accept-btn,.task-secondary-btn,.cad-select{color:#f3f6f9;background:#1e252be6;border:1px solid #fff3;width:100%;padding:8px 10px}.task-accept-btn,.task-secondary-btn{cursor:pointer}.task-accept-btn:hover,.task-secondary-btn:hover{background:#2e3942f2}.task-accept-btn:disabled,.task-secondary-btn:disabled{opacity:.55;cursor:default}.task-status-message{color:#cdd6dd;min-height:18px;margin-bottom:10px;font-size:12px}.task-status-message[data-type=success]{color:#79d28a}.task-status-message[data-type=error]{color:#ff8a80}.task-list{flex-direction:column;gap:10px;display:flex}.task-action-stack,.task-action-row{flex-direction:column;gap:8px;display:flex}.task-action-row{flex-direction:row}.task-card{background:#0c10149e;border:1px solid #ffffff14;padding:10px}.task-card-header{justify-content:space-between;gap:8px;margin-bottom:8px;display:flex}.task-type{opacity:.7;text-transform:uppercase;font-size:11px}.task-description{margin:0 0 8px;font-size:12px;line-height:1.4}.task-meta{opacity:.8;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:11px;display:flex}.task-secondary-btn{background:#3c302deb}.roster-summary-card{background:#10171dd1;border:1px solid #ffffff14;padding:10px}.roster-member-card{background:#0c1014bd}.roster-leader-badge{color:var(--accent);letter-spacing:.06em;text-transform:uppercase;background:#0f283ad1;border:1px solid #5bbbff47;align-items:center;padding:2px 8px;font-size:10px;font-weight:700;display:inline-flex} \ No newline at end of file +html,body{background:var(--panel);border-left:1px solid var(--stroke);width:100%;height:100%;box-shadow:var(--shadow);-webkit-backdrop-filter:blur(12px);margin:0;padding:0;overflow:hidden}body{opacity:1;visibility:visible}.panel-header{border-bottom:1px solid var(--stroke);background:linear-gradient(#ffffff0d,#0000);justify-content:space-between;align-items:center;padding:14px;display:flex}.panel-header h3{color:var(--accent);text-transform:uppercase;letter-spacing:.8px;font-size:14px;font-weight:650}.panel-content{height:calc(100% - 56px);padding:14px;overflow:auto}.placeholder-message{text-align:center;padding:20px}.placeholder-message p{color:var(--muted);font-size:13px;font-style:italic}.cad-tabs{grid-template-columns:repeat(4,1fr);gap:5px;margin-bottom:12px;display:grid}.cad-tabs.is-two-col{grid-template-columns:repeat(2,1fr)}.cad-tabs.is-three-col{grid-template-columns:repeat(3,1fr)}.cad-tab{color:#f3f6f9c7;text-transform:uppercase;letter-spacing:.08em;white-space:nowrap;cursor:pointer;background:#141b21e0;border:1px solid #ffffff24;min-width:0;padding:8px 7px;font-size:10px}.cad-tab:hover{color:#f3f6f9;background:#1f282ff0}.cad-tab.is-active{color:var(--accent);background:#0f283af5;border-color:#5bbbff6b}.cad-tab-panels{min-height:0}.cad-section{display:none}.cad-section.is-active{display:block}.cad-section-header{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;font-size:12px;font-weight:700}.task-accept-btn,.task-secondary-btn,.cad-select{color:#f3f6f9;background:#1e252be6;border:1px solid #fff3;width:100%;padding:8px 10px}.task-accept-btn,.task-secondary-btn{cursor:pointer}.task-accept-btn:hover,.task-secondary-btn:hover{background:#2e3942f2}.task-accept-btn:disabled,.task-secondary-btn:disabled{opacity:.55;cursor:default}.task-status-message{color:#cdd6dd;min-height:18px;margin-bottom:10px;font-size:12px}.task-status-message[data-type=success]{color:#79d28a}.task-status-message[data-type=error]{color:#ff8a80}.cad-modal{z-index:40;position:fixed;inset:0}.cad-modal.is-hidden{display:none}.cad-modal-backdrop{background:#04080cc2;position:absolute;inset:0}.cad-modal-dialog{background:#0b1118fa;border:1px solid #ffffff1f;width:min(480px,100% - 28px);margin:32px auto 0;position:relative;box-shadow:0 24px 64px #0000006b}.cad-modal-header,.cad-modal-actions{justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;display:flex}.cad-modal-header{border-bottom:1px solid #ffffff14}.cad-modal-header h3{margin:4px 0 0;font-size:18px;font-weight:650}.cad-modal-body{max-height:62vh;padding:14px;overflow:auto}.cad-modal-fields{gap:10px;display:grid}.cad-field{gap:6px;display:grid}.cad-field span{text-transform:uppercase;letter-spacing:.08em;color:#e9f1f8b3;font-size:11px;font-weight:700}.cad-input,.cad-textarea{color:#f3f6f9;box-sizing:border-box;width:100%;font:inherit;background:#1e252be6;border:1px solid #fff3;padding:8px 10px}.cad-textarea{resize:vertical;min-height:74px}.cad-icon-btn{width:30px;height:30px;color:var(--text);cursor:pointer;background:#181f28eb;border:1px solid #ffffff24;padding:0}.cad-modal-actions{border-top:1px solid #ffffff14;justify-content:flex-end}.cad-danger-alert{color:#ffd4cf;letter-spacing:.06em;text-transform:uppercase;background:linear-gradient(90deg,#5c1212f0,#801d1dd1);border:1px solid #ff6b6b5c;margin-bottom:10px;padding:8px 10px;font-size:11px;font-weight:700;animation:1.35s ease-in-out infinite cad-danger-pulse}.cad-danger-alert.is-hidden{display:none}.cad-warning-alert{color:#ffe9b2;letter-spacing:.06em;text-transform:uppercase;background:linear-gradient(90deg,#59400cf0,#7d5c12d6);border:1px solid #f6c65466;margin-bottom:10px;padding:8px 10px;font-size:11px;font-weight:700;animation:1.35s ease-in-out infinite cad-warning-pulse}.cad-warning-alert.is-hidden{display:none}.task-list{flex-direction:column;gap:10px;display:flex}.cad-request-actions{gap:8px;display:grid}.cad-request-btn{text-align:left}.task-action-stack,.task-action-row{flex-direction:column;gap:8px;display:flex}.task-action-row{flex-direction:row}.task-card{background:#0c10149e;border:1px solid #ffffff14;padding:10px}.task-card.is-danger,.roster-summary-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}.task-card-header{justify-content:space-between;gap:8px;margin-bottom:8px;display:flex}.task-type{opacity:.7;text-transform:uppercase;font-size:11px}.task-description{margin:0 0 8px;font-size:12px;line-height:1.4}.task-meta{opacity:.8;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:11px;display:flex}.task-secondary-btn{background:#3c302deb}.roster-summary-card{background:#10171dd1;border:1px solid #ffffff14;padding:10px}.task-alert-badge{color:#ffd8d1;letter-spacing:.08em;text-transform:uppercase;background:#5f1717e0;border:1px solid #ff6b6b70;align-items:center;padding:2px 8px;font-size:10px;font-weight:700;display:inline-flex}.roster-member-card{background:#0c1014bd}.dispatch-map-group-card{text-align:left;-webkit-appearance:none;appearance:none;width:100%;color:var(--text);font:inherit;cursor:pointer;border-radius:0;transition:border-color .12s,background .12s,transform .12s}.dispatch-map-group-card strong{color:var(--text)}.dispatch-map-group-card .task-type{color:var(--accent);opacity:.9}.dispatch-map-group-card .task-meta{color:var(--muted);opacity:1}.dispatch-map-group-card:hover{background:#121d26e6;border-color:#5bbbff42;transform:translate(-2px)}.dispatch-map-group-card.is-selected{background:#0f283aeb;border-color:#5bbbff85;box-shadow:inset 0 0 0 1px #5bbbff2e}.dispatch-map-group-card.is-danger:not(.is-selected){background:linear-gradient(#451416c7,#1c1115eb);border-color:#ff6b6b57}.dispatch-map-group-card.is-danger .task-meta,.roster-summary-card.is-danger .task-meta{color:#ffe8e4d1}.dispatch-map-card{text-align:left;-webkit-appearance:none;appearance:none;width:100%;color:var(--text);font:inherit;cursor:pointer;border-radius:0;transition:border-color .12s,background .12s,transform .12s}.dispatch-map-card strong{color:var(--text)}.dispatch-map-card .task-type{color:var(--accent);opacity:.9}.dispatch-map-card .task-description{color:var(--muted)}.dispatch-map-card .task-meta{color:var(--muted);opacity:1}.dispatch-map-card:hover{background:#121d26e6;border-color:#5bbbff42;transform:translate(-2px)}.dispatch-map-card.is-selected{background:#0f283aeb;border-color:#5bbbff85;box-shadow:inset 0 0 0 1px #5bbbff2e}.dispatch-map-card.is-warning:not(.is-selected){background:linear-gradient(#564011c7,#221b10eb);border-color:#f6c65457}.dispatch-map-card.is-warning .task-meta,.dispatch-map-card.is-warning .task-description{color:#fff3d6d6}.roster-leader-badge{color:var(--accent);letter-spacing:.06em;text-transform:uppercase;background:#0f283ad1;border:1px solid #5bbbff47;align-items:center;padding:2px 8px;font-size:10px;font-weight:700;display:inline-flex}@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 14px #ff6b6b24}}@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-sidepanel.js b/arma/client/addons/cad/ui/_site/cad-sidepanel.js index c84d019..abba8a2 100644 --- a/arma/client/addons/cad/ui/_site/cad-sidepanel.js +++ b/arma/client/addons/cad/ui/_site/cad-sidepanel.js @@ -1 +1 @@ -window.cadTasks={contracts:[],groups:[],activity:[],session:{},mode:"operations",dispatchView:"board",activeTab:"contracts",statuses:["available","en_route","on_task","holding","danger","unavailable"],roles:["infantry","recon","armor","air","logistics","support"],init(){document.querySelectorAll(".cad-tab").forEach(s=>{s.addEventListener("click",()=>{this.setActiveTab(s.dataset.tab||"contracts")})}),window.ForgeBridge.on("cad::hydrate",s=>{this.setHydratePayload(s||{})}),window.ForgeBridge.on("cad::assignment::response",s=>{this.handleServerResponse(!!s.success,s.message||"")}),window.ForgeBridge.on("cad::group::response",s=>{this.handleServerResponse(!!s.success,s.message||"")}),window.ForgeBridge.ready({loaded:!0})},setActiveTab(s){this.activeTab=s||"contracts",document.querySelectorAll(".cad-tab").forEach(s=>{s.classList.toggle("is-active",s.dataset.tab===this.activeTab)}),document.querySelectorAll("[data-panel]").forEach(s=>{s.classList.toggle("is-active",s.dataset.panel===this.activeTab)})},syncLayoutState(){const s=document.querySelector(".cad-tabs"),t=document.getElementById("contractsPanel"),e=document.getElementById("rosterPanel"),a=document.getElementById("activityPanel"),n=e?.querySelector(".cad-section-header");if(this.isDispatchMapMode())return s&&(s.style.display="none"),t&&(t.classList.remove("is-active"),t.style.display="none"),a&&(a.classList.remove("is-active"),a.style.display="none"),e&&(e.classList.add("is-active"),e.style.display="block"),n&&(n.textContent="Active Groups"),void(this.activeTab="roster");s&&(s.style.display=""),t&&(t.style.display=""),a&&(a.style.display=""),e&&(e.style.display=""),n&&(n.textContent="Roster")},setHydratePayload(s){this.contracts=Array.isArray(s.contracts)?s.contracts:[],this.groups=Array.isArray(s.groups)?s.groups:[],this.activity=Array.isArray(s.activity)?s.activity:[],this.session=s.session&&"object"==typeof s.session?s.session:{},this.mode=s&&"string"==typeof s.mode?s.mode:"operations",this.dispatchView=s&&"string"==typeof s.dispatchView?s.dispatchView:"board";const t=document.getElementById("cadStatusMessage");!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),"dispatch"===this.mode&&"map"===this.dispatchView&&(this.activeTab="roster"),this.render()},setStatus(s,t){const e=document.getElementById("cadStatusMessage");e&&(e.textContent=s||"",e.dataset.type=t||"")},handleServerResponse(s,t){this.setStatus(t||(s?"CAD update succeeded.":"CAD update failed."),s?"success":"error")},acknowledgeTask(s){this.setStatus("Acknowledging contract...","info"),window.mapUI.sendEvent("cad::tasks::acknowledge",{taskID:s})},declineTask(s){this.setStatus("Declining contract...","info"),window.mapUI.sendEvent("cad::tasks::decline",{taskID:s})},updateGroupStatus(s,t){this.setStatus("Updating group status...","info"),window.mapUI.sendEvent("cad::groups::status",{groupID:s,status:t})},updateGroupRole(s,t){this.setStatus("Updating group role...","info"),window.mapUI.sendEvent("cad::groups::role",{groupID:s,role:t})},getPlayerGroupId(){return this.session.groupId||""},getCurrentGroup(){const s=this.getPlayerGroupId();return this.groups.find(t=>t.groupId===s)||null},normalizeCollection:s=>Array.isArray(s)?s:s&&"object"==typeof s?Object.values(s):[],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 s=document.getElementById("taskList");if(!s)return;if(this.isDispatchMapMode())return void(s.innerHTML='

Use the dispatch board view to assign and review contracts.

');const t=this.getPlayerGroupId(),e=this.contracts.filter(s=>(s.assignedGroupId||"")===t);e.length?s.innerHTML=e.map(s=>{const e=s.taskId||s.taskID||"",a=Array.isArray(s.position)?s.position:[0,0,0],n=s.assignedGroupId||"",i=s.assignmentState||"unassigned",r=this.groups.find(s=>s.groupId===n),o=this.isLeader()&&n===t;return`\n
\n
\n ${s.title||e}\n ${s.type||"task"}\n
\n

${s.description||""}

\n
\n ${"unassigned"===i?"Available":`${i}: ${r?r.callsign:n}`}\n X: ${Math.round(a[0]||0)} Y: ${Math.round(a[1]||0)}\n
\n ${o&&"assigned"===i?`
\n \n \n
`:""}\n
\n `}).join(""):s.innerHTML='

No contract is currently assigned to your group.

'},renderRoster(){const s=document.getElementById("rosterList");if(!s)return;if(this.isDispatchMapMode())return this.groups.length?void(s.innerHTML=this.groups.map(s=>`\n
\n
\n ${s.callsign||s.groupId||"Unknown Group"}\n ${s.role||"group"}\n
\n
\n Leader: ${s.leaderName||"Unknown"}\n Status: ${s.status||"unknown"}\n
\n
\n Members: ${this.normalizeCollection(s.members).length}\n Task: ${s.currentTaskId||"None"}\n
\n
\n `).join("")):void(s.innerHTML='

No active groups are currently available.

');const t=this.getCurrentGroup();if(!t)return void(s.innerHTML='

Your group is not currently available.

');const e=this.normalizeCollection(t.members);e.length?s.innerHTML=`\n
\n
\n ${t.callsign||t.groupId||"Current Group"}\n ${e.length} member${1===e.length?"":"s"}\n
\n
\n Leader: ${t.leaderName||"Unknown"}\n Status: ${t.status||"unknown"}\n
\n
\n Role: ${t.role||"unassigned"}\n Task: ${t.currentTaskId||"None"}\n
\n
\n ${e.map(s=>{const t=(s.lifeState||"unknown").replaceAll("_"," "),e=s.isLeader?'Leader':"";return`\n
\n
\n ${s.name||"Unknown Operator"}\n ${t}\n
\n
\n ${s.uid||"No UID"}\n ${e}\n
\n
\n `}).join("")}\n `:s.innerHTML='

No roster members are currently available.

'},renderActivity(){const s=document.getElementById("activityList");s&&(this.activity.length?s.innerHTML=this.activity.slice().reverse().slice(0,8).map(s=>`\n
\n
\n ${s.type||"activity"}\n ${Math.round(s.timestamp||0)}s\n
\n

${s.message||""}

\n
\n `).join(""):s.innerHTML='

No recent activity.

')},render(){this.syncLayoutState(),this.renderContracts(),this.renderRoster(),this.renderActivity(),this.isDispatchMapMode()||this.setActiveTab(this.activeTab)}},window.cadTasks.init(); \ No newline at end of file +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:!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");!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),this.selectedDispatchGroupId&&!this.groups.some(e=>e.groupId===this.selectedDispatchGroupId)&&(this.selectedDispatchGroupId=""),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 \n `:"textarea"===e.type?`\n \n `:`\n \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 \n
\n ${e.title||t||"Support Request"}\n ${this.getRequestTypeLabel(e.type||"request")}\n
\n

${e.summary||""}

\n
\n Group: ${e.groupCallsign||e.groupId||"Unknown"}\n ${(e.priority||"priority").replaceAll("_"," ")}\n
\n
\n ${window.mapUI.formatPosition(s)}\n ${t||"request"}\n
\n \n `}).join("")):void(e.innerHTML='

No support requests are currently active.

')}const t=this.canSubmitSupportRequest()?`\n
\n ${this.requestTypes.map(e=>`\n \n ${e.label}\n \n `).join("")}\n
\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";return`\n
\n
\n ${e.title||this.getRequestTypeLabel(e.type||"")}\n ${(e.priority||"priority").replaceAll("_"," ")}\n
\n

${e.summary||""}

\n
\n Group: ${e.groupCallsign||e.groupId||"Unknown"}\n ${this.getRequestTypeLabel(e.type||"")}\n
\n ${s?`
\n \n
`:""}\n
\n `}).join("")}\n `:e.innerHTML=`\n ${t}\n

No support requests are currently active.

\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="";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()},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="";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="";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='

No contracts are currently available.

');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 \n
\n ${e.title||t}\n ${this.formatTypeLabel(e)}\n
\n

${e.description||""}

\n
\n ${o}\n ${window.mapUI.formatPosition(s)}\n
\n \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
\n
\n ${e.title||s}\n ${this.formatTypeLabel(e)}\n
\n

${e.description||""}

\n
\n ${"unassigned"===i?"Available":`${i}: ${r?r.callsign:n}`}\n ${window.mapUI.formatPosition(a)}\n
\n ${o&&"assigned"===i?`
\n \n \n
`:""}\n
\n `}).join(""):e.innerHTML='

No contract is currently assigned to your group.

'},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 \n
\n ${e.callsign||e.groupId||"Unknown Group"}\n ${e.role||"group"}\n ${s?'Danger':""}\n
\n
\n Leader: ${e.leaderName||"Unknown"}\n Status: ${e.status||"unknown"}\n
\n
\n Members: ${this.normalizeCollection(e.members).length}\n Task: ${e.currentTaskId||"None"}\n
\n \n `}).join("")):void(e.innerHTML='

No active groups are currently available.

');const t=this.getCurrentGroup();if(!t)return void(e.innerHTML='

Your group is not currently available.

');const s=this.normalizeCollection(t.members),a="danger"===(t.status||"");s.length?e.innerHTML=`\n
\n
\n ${t.callsign||t.groupId||"Current Group"}\n ${s.length} member${1===s.length?"":"s"}\n ${a?'Danger':""}\n
\n
\n Leader: ${t.leaderName||"Unknown"}\n Status: ${t.status||"unknown"}\n
\n
\n Role: ${t.role||"unassigned"}\n Task: ${t.currentTaskId||"None"}\n
\n
\n ${s.map(e=>{const t=(e.lifeState||"unknown").replaceAll("_"," "),s=e.isLeader?'Leader':"";return`\n
\n
\n ${e.name||"Unknown Operator"}\n ${t}\n
\n
\n ${e.uid||"No UID"}\n ${s}\n
\n
\n `}).join("")}\n `:e.innerHTML='

No roster members are currently available.

'},renderActivity(){const e=document.getElementById("activityList");e&&(this.activity.length?e.innerHTML=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(""):e.innerHTML='

No recent activity.

')},render(){this.updateDangerAlert(),this.updateRequestAlert(),this.syncLayoutState(),this.renderContracts(),this.renderRoster(),this.renderRequests(),this.renderActivity(),this.setActiveTab(this.activeTab)}},window.cadTasks.init(); \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-topbar.js b/arma/client/addons/cad/ui/_site/cad-topbar.js index 919a290..6d3986f 100644 --- a/arma/client/addons/cad/ui/_site/cad-topbar.js +++ b/arma/client/addons/cad/ui/_site/cad-topbar.js @@ -1 +1 @@ -window.cadTopbar={mode:"operations",dispatchView:"board",currentGroup:null,session:{},init(){document.getElementById("btnClose").addEventListener("click",()=>{window.mapUI.sendEvent("map::close",null)}),document.getElementById("modeToggle").addEventListener("change",e=>{window.mapUI.sendEvent("cad::mode::set",{mode:e.target.checked?"dispatch":"operations"})}),document.getElementById("dispatchRefreshBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::refresh",{})}),document.getElementById("dispatchBoardBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"board"})}),document.getElementById("dispatchMapBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"map"})}),document.getElementById("operatorRoleBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::role",{groupID:this.currentGroup.groupId||"",role:document.getElementById("operatorRoleSelect").value})}),document.getElementById("operatorStatusBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::status",{groupID:this.currentGroup.groupId||"",status:document.getElementById("operatorStatusSelect").value})}),window.mapUI.sendEvent("cad::topbar::ready",{})},formatLocation(e){const t=Array.isArray(e?.position)?e.position:[0,0,0];return`X: ${Math.round(t[0]||0).toString().padStart(4,"0")} Y: ${Math.round(t[1]||0).toString().padStart(4,"0")}`},receiveState(e){this.session=e&&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",this.currentGroup=e&&e.currentGroup&&"object"==typeof e.currentGroup?e.currentGroup:null;const t=document.getElementById("modeControls"),o=!!this.session.isDispatcher,s=!(!this.currentGroup||!this.session.isLeader&&!this.session.isDispatcher),n=document.getElementById("operatorStrip"),d=document.getElementById("operatorControls"),r=document.getElementById("dispatchViewControls"),i=document.getElementById("dispatchRefreshBtn"),a=document.getElementById("dispatchBoardBtn"),c=document.getElementById("dispatchMapBtn");t.classList.toggle("is-hidden",!o),r.classList.toggle("is-hidden",!o||"dispatch"!==this.mode),n.classList.toggle("is-hidden","operations"!==this.mode||!this.currentGroup),d.classList.toggle("is-hidden",!s),document.body.dataset.mode=this.mode,document.body.dataset.dispatcher=o?"true":"false",document.getElementById("modeToggle").checked="dispatch"===this.mode,a.classList.toggle("is-active","board"===this.dispatchView),c.classList.toggle("is-active","map"===this.dispatchView),i.title="dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD",i.setAttribute("aria-label","dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD"),document.getElementById("operatorGroupName").textContent=this.currentGroup?this.currentGroup.callsign||this.currentGroup.groupId||"Current Group":"No Group",document.getElementById("operatorLocation").textContent=this.currentGroup?this.formatLocation(this.currentGroup):"Unavailable",this.currentGroup&&(document.getElementById("operatorRoleSelect").value=this.currentGroup.role||"infantry",document.getElementById("operatorStatusSelect").value=this.currentGroup.status||"available")}},window.cadTopbar.init(); \ No newline at end of file +window.cadTopbar={mode:"operations",dispatchView:"board",currentGroup:null,session:{},init(){document.getElementById("btnClose").addEventListener("click",()=>{window.mapUI.sendEvent("map::close",null)}),document.getElementById("modeToggle").addEventListener("change",e=>{window.mapUI.sendEvent("cad::mode::set",{mode:e.target.checked?"dispatch":"operations"})}),document.getElementById("dispatchRefreshBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::refresh",{})}),document.getElementById("dispatchBoardBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"board"})}),document.getElementById("dispatchMapBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"map"})}),document.getElementById("operatorRoleBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::role",{groupID:this.currentGroup.groupId||"",role:document.getElementById("operatorRoleSelect").value})}),document.getElementById("operatorStatusBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::status",{groupID:this.currentGroup.groupId||"",status:document.getElementById("operatorStatusSelect").value})}),window.mapUI.sendEvent("cad::topbar::ready",{})},formatLocation(e){const t=Array.isArray(e?.position)?e.position:[0,0,0];return window.mapUI.formatPosition(t)},receiveState(e){this.session=e&&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",this.currentGroup=e&&e.currentGroup&&"object"==typeof e.currentGroup?e.currentGroup:null;const t=document.getElementById("modeControls"),o=!!this.session.isDispatcher,s=!(!this.currentGroup||!this.session.isLeader&&!this.session.isDispatcher),n=document.getElementById("operatorStrip"),d=document.getElementById("operatorControls"),i=document.getElementById("dispatchViewControls"),r=document.getElementById("dispatchRefreshBtn"),a=document.getElementById("dispatchBoardBtn"),c=document.getElementById("dispatchMapBtn");t.classList.toggle("is-hidden",!o),i.classList.toggle("is-hidden",!o||"dispatch"!==this.mode),n.classList.toggle("is-hidden","operations"!==this.mode||!this.currentGroup),d.classList.toggle("is-hidden",!s),document.body.dataset.mode=this.mode,document.body.dataset.dispatcher=o?"true":"false",document.getElementById("modeToggle").checked="dispatch"===this.mode,a.classList.toggle("is-active","board"===this.dispatchView),c.classList.toggle("is-active","map"===this.dispatchView),r.title="dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD",r.setAttribute("aria-label","dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD"),document.getElementById("operatorGroupName").textContent=this.currentGroup?this.currentGroup.callsign||this.currentGroup.groupId||"Current Group":"No Group",document.getElementById("operatorLocation").textContent=this.currentGroup?this.formatLocation(this.currentGroup):"Unavailable",this.currentGroup&&(document.getElementById("operatorRoleSelect").value=this.currentGroup.role||"infantry",document.getElementById("operatorStatusSelect").value=this.currentGroup.status||"available")}},window.cadTopbar.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 b06c997..680ae6a 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
Groups In Danger 0

Available Contracts

Assigned Contracts

Group Board

Activity Feed

\ 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/_site/sidepanel.html b/arma/client/addons/cad/ui/_site/sidepanel.html index aa80c55..5afd6e0 100644 --- a/arma/client/addons/cad/ui/_site/sidepanel.html +++ b/arma/client/addons/cad/ui/_site/sidepanel.html @@ -1 +1 @@ -

CAD System

Contracts

Loading contracts...

Roster

Loading roster...

Activity

No recent activity.

\ No newline at end of file +

CAD System

Contracts

Loading contracts...

Roster

Loading roster...

Support Requests

No support requests.

Activity

No recent 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 207ed7a..51dd586 100644 --- a/arma/client/addons/cad/ui/src/dispatcher.html +++ b/arma/client/addons/cad/ui/src/dispatcher.html @@ -13,6 +13,14 @@
+ +
@@ -27,7 +35,11 @@ Active Groups 0
-
+
+ Open Requests + 0 +
+
Groups In Danger 0
@@ -37,6 +49,15 @@

Available Contracts

+
-

Activity Feed

+

Requests & Activity

@@ -146,6 +167,160 @@
+ + + +