Add CAD request workflows and focus actions
- Add request hydration, submission, closing, and response handling - Wire UI events for dispatch orders, support requests, and map focus - Expand dispatcher layout for alerts, requests, and detail views
This commit is contained in:
parent
112488f82e
commit
4ea7cf7d05
@ -24,3 +24,17 @@ if (isNil QGVAR(CADUIBridge)) then { call FUNC(initUIBridge); };
|
|||||||
|
|
||||||
GVAR(CADUIBridge) call ["handleGroupUpdateResponse", [_result]];
|
GVAR(CADUIBridge) call ["handleGroupUpdateResponse", [_result]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] 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);
|
||||||
|
|||||||
@ -72,6 +72,48 @@ switch (_event) do {
|
|||||||
|
|
||||||
GVAR(CADUIBridge) call ["requestAssignTask", [_taskID, _groupID, _note]];
|
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": {
|
case "cad::tasks::acknowledge": {
|
||||||
private _taskID = "";
|
private _taskID = "";
|
||||||
if (_data isEqualType createHashMap) then {
|
if (_data isEqualType createHashMap) then {
|
||||||
@ -108,6 +150,42 @@ switch (_event) do {
|
|||||||
|
|
||||||
GVAR(CADUIBridge) call ["requestGroupRole", [_groupID, _role]];
|
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": {
|
case "map::zoomIn": {
|
||||||
private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull];
|
private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull];
|
||||||
if (isNull _mapCtrl) exitWith {};
|
if (isNull _mapCtrl) exitWith {};
|
||||||
|
|||||||
@ -27,6 +27,7 @@ GVAR(CADRepository) = createHashMapObject [[
|
|||||||
_self set ["isOpen", false];
|
_self set ["isOpen", false];
|
||||||
_self set ["groups", []];
|
_self set ["groups", []];
|
||||||
_self set ["contracts", []];
|
_self set ["contracts", []];
|
||||||
|
_self set ["requests", []];
|
||||||
_self set ["assignments", []];
|
_self set ["assignments", []];
|
||||||
_self set ["activity", []];
|
_self set ["activity", []];
|
||||||
_self set ["session", createHashMap];
|
_self set ["session", createHashMap];
|
||||||
@ -37,6 +38,7 @@ GVAR(CADRepository) = createHashMapObject [[
|
|||||||
createHashMapFromArray [
|
createHashMapFromArray [
|
||||||
["groups", +(_self getOrDefault ["groups", []])],
|
["groups", +(_self getOrDefault ["groups", []])],
|
||||||
["contracts", +(_self getOrDefault ["contracts", []])],
|
["contracts", +(_self getOrDefault ["contracts", []])],
|
||||||
|
["requests", +(_self getOrDefault ["requests", []])],
|
||||||
["assignments", +(_self getOrDefault ["assignments", []])],
|
["assignments", +(_self getOrDefault ["assignments", []])],
|
||||||
["activity", +(_self getOrDefault ["activity", []])],
|
["activity", +(_self getOrDefault ["activity", []])],
|
||||||
["session", +(_self getOrDefault ["session", createHashMap])],
|
["session", +(_self getOrDefault ["session", createHashMap])],
|
||||||
@ -67,6 +69,7 @@ GVAR(CADRepository) = createHashMapObject [[
|
|||||||
|
|
||||||
_self set ["groups", +(_payload getOrDefault ["groups", []])];
|
_self set ["groups", +(_payload getOrDefault ["groups", []])];
|
||||||
_self set ["contracts", +(_payload getOrDefault ["contracts", []])];
|
_self set ["contracts", +(_payload getOrDefault ["contracts", []])];
|
||||||
|
_self set ["requests", +(_payload getOrDefault ["requests", []])];
|
||||||
_self set ["assignments", +(_payload getOrDefault ["assignments", []])];
|
_self set ["assignments", +(_payload getOrDefault ["assignments", []])];
|
||||||
_self set ["activity", +(_payload getOrDefault ["activity", []])];
|
_self set ["activity", +(_payload getOrDefault ["activity", []])];
|
||||||
_self set ["session", +(_payload getOrDefault ["session", createHashMap])];
|
_self set ["session", +(_payload getOrDefault ["session", createHashMap])];
|
||||||
|
|||||||
@ -213,6 +213,47 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
[SRPC(cad,requestAssignCadTask), [getPlayerUID player, _taskID, _groupID, _note]] call CFUNC(serverEvent);
|
[SRPC(cad,requestAssignCadTask), [getPlayerUID player, _taskID, _groupID, _note]] call CFUNC(serverEvent);
|
||||||
true
|
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 {
|
["requestAcknowledgeTask", compileFinal {
|
||||||
params [["_taskID", "", [""]]];
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
@ -245,6 +286,87 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
[SRPC(cad,requestUpdateCadGroupRole), [getPlayerUID player, _groupID, _role]] call CFUNC(serverEvent);
|
[SRPC(cad,requestUpdateCadGroupRole), [getPlayerUID player, _groupID, _role]] call CFUNC(serverEvent);
|
||||||
true
|
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 {
|
["refreshHydrate", compileFinal {
|
||||||
if (isNil QGVAR(CADRepository)) exitWith { false };
|
if (isNil QGVAR(CADRepository)) exitWith { false };
|
||||||
GVAR(CADRepository) call ["pushHydratePayload", [_self]]
|
GVAR(CADRepository) call ["pushHydratePayload", [_self]]
|
||||||
@ -301,6 +423,25 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["message", _result getOrDefault ["message", "Group update processed."]],
|
["message", _result getOrDefault ["message", "Group update processed."]],
|
||||||
["success", _result getOrDefault ["success", false]]
|
["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]]
|
||||||
|
]]]
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -89,9 +89,9 @@ class RscMapUI {
|
|||||||
class SidePanelBrowser: RscText {
|
class SidePanelBrowser: RscText {
|
||||||
type = 106;
|
type = 106;
|
||||||
idc = 1005;
|
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
|
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
|
h = "(safeZoneH * 0.8) - 0.10372 - 0.0556"; // Full height minus visible bars
|
||||||
colorBackground[] = {0, 0, 0, 0};
|
colorBackground[] = {0, 0, 0, 0};
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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)};
|
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)};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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();
|
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();
|
||||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
<!doctype html><html><head><meta charset="UTF-8"></head><body><div class="panel-header"><h3>CAD System</h3></div><div class="panel-content"><div id="cadStatusMessage" class="task-status-message"></div><div class="cad-tabs" role="tablist" aria-label="CAD Sections"><button id="tabContractsBtn" class="cad-tab is-active" type="button" data-tab="contracts">Contracts</button> <button id="tabRosterBtn" class="cad-tab" type="button" data-tab="roster">Roster</button> <button id="tabActivityBtn" class="cad-tab" type="button" data-tab="activity">Activity</button></div><div class="cad-tab-panels"><div id="contractsPanel" class="cad-section is-active" data-panel="contracts"><div class="cad-section-header">Contracts</div><div id="taskList" class="task-list"><div class="placeholder-message"><p>Loading contracts...</p></div></div></div><div id="rosterPanel" class="cad-section" data-panel="roster"><div class="cad-section-header">Roster</div><div id="rosterList" class="task-list"><div class="placeholder-message"><p>Loading roster...</p></div></div></div><div id="activityPanel" class="cad-section" data-panel="activity"><div class="cad-section-header">Activity</div><div id="activityList" class="task-list"><div class="placeholder-message"><p>No recent activity.</p></div></div></div></div></div><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const d=document.createElement("style");d.textContent=e,document.head.appendChild(d)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,d)=>e.then(()=>d.endsWith(".css")?this.loadCSS(d):d.endsWith(".js")?this.loadJS(d):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.js"]).catch(e=>console.error("[SIDEPANEL] Load error:",e))</script></body></html>
|
<!doctype html><html><head><meta charset="UTF-8"></head><body><div class="panel-header"><h3>CAD System</h3></div><div class="panel-content"><div id="cadStatusMessage" class="task-status-message"></div><div id="cadDangerAlert" class="cad-danger-alert is-hidden"></div><div id="cadRequestAlert" class="cad-warning-alert is-hidden"></div><div class="cad-tabs" role="tablist" aria-label="CAD Sections"><button id="tabContractsBtn" class="cad-tab is-active" type="button" data-tab="contracts">Contracts</button> <button id="tabRosterBtn" class="cad-tab" type="button" data-tab="roster">Roster</button> <button id="tabRequestsBtn" class="cad-tab" type="button" data-tab="requests">Requests</button> <button id="tabActivityBtn" class="cad-tab" type="button" data-tab="activity">Activity</button></div><div class="cad-tab-panels"><div id="contractsPanel" class="cad-section is-active" data-panel="contracts"><div class="cad-section-header">Contracts</div><div id="taskList" class="task-list"><div class="placeholder-message"><p>Loading contracts...</p></div></div></div><div id="rosterPanel" class="cad-section" data-panel="roster"><div class="cad-section-header">Roster</div><div id="rosterList" class="task-list"><div class="placeholder-message"><p>Loading roster...</p></div></div></div><div id="requestsPanel" class="cad-section" data-panel="requests"><div class="cad-section-header">Support Requests</div><div id="requestList" class="task-list"><div class="placeholder-message"><p>No support requests.</p></div></div></div><div id="activityPanel" class="cad-section" data-panel="activity"><div class="cad-section-header">Activity</div><div id="activityList" class="task-list"><div class="placeholder-message"><p>No recent activity.</p></div></div></div></div></div><div id="cadRequestModal" class="cad-modal is-hidden"><div class="cad-modal-backdrop"></div><div class="cad-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="cadRequestModalTitle"><div class="cad-modal-header"><div><div class="cad-section-header">Support Request</div><h3 id="cadRequestModalTitle">Submit Request</h3></div><button id="cadRequestModalCloseBtn" class="cad-icon-btn" type="button" aria-label="Close support request form">x</button></div><div class="cad-modal-body"><div class="cad-modal-fields"><label class="cad-field"><span>Priority</span> <select id="cadRequestPrioritySelect" class="cad-select"><option value="routine">routine</option><option value="priority" selected>priority</option><option value="emergency">emergency</option></select></label><div id="cadRequestFields" class="cad-modal-fields"></div></div></div><div class="cad-modal-actions"><button id="cadRequestModalSaveBtn" type="button" class="task-accept-btn">Submit Request</button></div></div></div><script>window.MapLoader={loadCSS:e=>A3API.RequestFile(e).then(e=>{const d=document.createElement("style");d.textContent=e,document.head.appendChild(d)}),loadJS(path){return A3API.RequestFile(path).then(js=>{eval(js)})},loadAll(e){return e.reduce((e,d)=>e.then(()=>d.endsWith(".css")?this.loadCSS(d):d.endsWith(".js")?this.loadJS(d):Promise.resolve()),Promise.resolve())}},MapLoader.loadAll(["forge\\forge_client\\addons\\cad\\ui\\_site\\cad-common.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.css","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-shared.js","forge\\forge_client\\addons\\cad\\ui\\_site\\cad-sidepanel.js"]).catch(e=>console.error("[SIDEPANEL] Load error:",e))</script></body></html>
|
||||||
@ -13,6 +13,14 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div id="dispatcherStatusMessage" class="dispatch-status"></div>
|
<div id="dispatcherStatusMessage" class="dispatch-status"></div>
|
||||||
|
<div
|
||||||
|
id="dispatcherDangerAlert"
|
||||||
|
class="dispatch-danger-alert is-hidden"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
id="dispatcherRequestAlert"
|
||||||
|
class="dispatch-warning-alert is-hidden"
|
||||||
|
></div>
|
||||||
|
|
||||||
<section class="dispatch-metrics">
|
<section class="dispatch-metrics">
|
||||||
<div class="metric-card">
|
<div class="metric-card">
|
||||||
@ -27,7 +35,11 @@
|
|||||||
<span class="metric-label">Active Groups</span>
|
<span class="metric-label">Active Groups</span>
|
||||||
<strong id="metricActiveGroups">0</strong>
|
<strong id="metricActiveGroups">0</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric-card">
|
<div id="metricOpenRequestsCard" class="metric-card">
|
||||||
|
<span class="metric-label">Open Requests</span>
|
||||||
|
<strong id="metricOpenRequests">0</strong>
|
||||||
|
</div>
|
||||||
|
<div id="metricDangerGroupsCard" class="metric-card">
|
||||||
<span class="metric-label">Groups In Danger</span>
|
<span class="metric-label">Groups In Danger</span>
|
||||||
<strong id="metricDangerGroups">0</strong>
|
<strong id="metricDangerGroups">0</strong>
|
||||||
</div>
|
</div>
|
||||||
@ -37,6 +49,15 @@
|
|||||||
<section class="dispatch-panel dispatch-panel-open">
|
<section class="dispatch-panel dispatch-panel-open">
|
||||||
<div class="dispatch-panel-header">
|
<div class="dispatch-panel-header">
|
||||||
<h3>Available Contracts</h3>
|
<h3>Available Contracts</h3>
|
||||||
|
<button
|
||||||
|
id="dispatcherCreateOrderBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
aria-label="Create dispatch order"
|
||||||
|
title="Create dispatch order"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="dispatcherOpenContracts"
|
id="dispatcherOpenContracts"
|
||||||
@ -63,7 +84,7 @@
|
|||||||
|
|
||||||
<section class="dispatch-panel dispatch-panel-activity">
|
<section class="dispatch-panel dispatch-panel-activity">
|
||||||
<div class="dispatch-panel-header">
|
<div class="dispatch-panel-header">
|
||||||
<h3>Activity Feed</h3>
|
<h3>Requests & Activity</h3>
|
||||||
</div>
|
</div>
|
||||||
<div id="dispatcherActivity" class="dispatch-list"></div>
|
<div id="dispatcherActivity" class="dispatch-list"></div>
|
||||||
</section>
|
</section>
|
||||||
@ -146,6 +167,160 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="dispatcherOrderModal" class="dispatch-modal is-hidden">
|
||||||
|
<div class="dispatch-modal-backdrop"></div>
|
||||||
|
<div
|
||||||
|
class="dispatch-modal-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="dispatcherOrderModalTitle"
|
||||||
|
>
|
||||||
|
<div class="dispatch-modal-header">
|
||||||
|
<div>
|
||||||
|
<p class="dispatch-kicker">Dispatch Order</p>
|
||||||
|
<h3 id="dispatcherOrderModalTitle">
|
||||||
|
Create Support Order
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="dispatcherOrderModalCloseBtn"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close dispatch order editor"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-body">
|
||||||
|
<div class="dispatch-modal-fields">
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Assignee Group</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherOrderAssigneeSelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
></select>
|
||||||
|
</label>
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Target Group</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherOrderTargetSelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
></select>
|
||||||
|
</label>
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Priority</span>
|
||||||
|
<select
|
||||||
|
id="dispatcherOrderPrioritySelect"
|
||||||
|
class="dispatch-select"
|
||||||
|
>
|
||||||
|
<option value="routine">routine</option>
|
||||||
|
<option value="priority" selected>
|
||||||
|
priority
|
||||||
|
</option>
|
||||||
|
<option value="emergency">emergency</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="dispatch-field">
|
||||||
|
<span>Order Note</span>
|
||||||
|
<textarea
|
||||||
|
id="dispatcherOrderNoteInput"
|
||||||
|
class="dispatch-textarea"
|
||||||
|
rows="4"
|
||||||
|
placeholder="Optional order note for the assigned group."
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-actions">
|
||||||
|
<button
|
||||||
|
id="dispatcherOrderModalSaveBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn"
|
||||||
|
>
|
||||||
|
Create Order
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dispatcherRequestModal" class="dispatch-modal is-hidden">
|
||||||
|
<div class="dispatch-modal-backdrop"></div>
|
||||||
|
<div
|
||||||
|
class="dispatch-modal-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="dispatcherRequestModalTitle"
|
||||||
|
>
|
||||||
|
<div class="dispatch-modal-header">
|
||||||
|
<div>
|
||||||
|
<p class="dispatch-kicker">Support Request</p>
|
||||||
|
<h3 id="dispatcherRequestModalTitle">
|
||||||
|
Request Details
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="dispatcherRequestModalCloseBtn"
|
||||||
|
class="dispatch-icon-btn"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close support request details"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-body">
|
||||||
|
<div class="dispatch-meta-grid">
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Title</span>
|
||||||
|
<strong id="dispatcherRequestTitle"
|
||||||
|
>Support Request</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Priority</span>
|
||||||
|
<strong id="dispatcherRequestPriority"
|
||||||
|
>priority</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Group</span>
|
||||||
|
<strong id="dispatcherRequestGroup"
|
||||||
|
>Unknown</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="metric-label">Type</span>
|
||||||
|
<strong id="dispatcherRequestType"
|
||||||
|
>request</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-field">
|
||||||
|
<span>Summary</span>
|
||||||
|
<div
|
||||||
|
id="dispatcherRequestSummary"
|
||||||
|
class="dispatch-detail-block"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-field">
|
||||||
|
<span>Submitted Fields</span>
|
||||||
|
<div
|
||||||
|
id="dispatcherRequestFields"
|
||||||
|
class="dispatch-detail-list"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-modal-actions">
|
||||||
|
<button
|
||||||
|
id="dispatcherRequestModalDoneBtn"
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
window.cadDispatcher = {
|
window.cadDispatcher = {
|
||||||
contracts: [],
|
contracts: [],
|
||||||
|
requests: [],
|
||||||
groups: [],
|
groups: [],
|
||||||
activity: [],
|
activity: [],
|
||||||
session: {},
|
session: {},
|
||||||
editingGroupId: "",
|
editingGroupId: "",
|
||||||
|
viewingRequestId: "",
|
||||||
statuses: [
|
statuses: [
|
||||||
"available",
|
"available",
|
||||||
"en_route",
|
"en_route",
|
||||||
@ -14,6 +16,12 @@ window.cadDispatcher = {
|
|||||||
],
|
],
|
||||||
roles: ["infantry", "recon", "armor", "air", "logistics", "support"],
|
roles: ["infantry", "recon", "armor", "air", "logistics", "support"],
|
||||||
init() {
|
init() {
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherCreateOrderBtn")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
this.openOrderModal();
|
||||||
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
.getElementById("dispatcherGroupModalCloseBtn")
|
.getElementById("dispatcherGroupModalCloseBtn")
|
||||||
.addEventListener("click", () => {
|
.addEventListener("click", () => {
|
||||||
@ -32,12 +40,49 @@ window.cadDispatcher = {
|
|||||||
this.closeGroupModal();
|
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", {});
|
window.mapUI.sendEvent("cad::dispatcher::ready", {});
|
||||||
},
|
},
|
||||||
receiveHydrate(payload) {
|
receiveHydrate(payload) {
|
||||||
this.contracts = Array.isArray(payload.contracts)
|
this.contracts = Array.isArray(payload.contracts)
|
||||||
? payload.contracts
|
? payload.contracts
|
||||||
: [];
|
: [];
|
||||||
|
this.requests = Array.isArray(payload.requests) ? payload.requests : [];
|
||||||
this.groups = Array.isArray(payload.groups) ? payload.groups : [];
|
this.groups = Array.isArray(payload.groups) ? payload.groups : [];
|
||||||
this.activity = Array.isArray(payload.activity) ? payload.activity : [];
|
this.activity = Array.isArray(payload.activity) ? payload.activity : [];
|
||||||
this.session =
|
this.session =
|
||||||
@ -54,6 +99,8 @@ window.cadDispatcher = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.syncOpenModal();
|
this.syncOpenModal();
|
||||||
|
this.syncOrderModal();
|
||||||
|
this.syncRequestModal();
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
setStatus(message, type) {
|
setStatus(message, type) {
|
||||||
@ -65,6 +112,288 @@ window.cadDispatcher = {
|
|||||||
statusEl.textContent = message || "";
|
statusEl.textContent = message || "";
|
||||||
statusEl.dataset.type = type || "";
|
statusEl.dataset.type = type || "";
|
||||||
},
|
},
|
||||||
|
getDangerGroups() {
|
||||||
|
return this.groups.filter((group) => (group.status || "") === "danger");
|
||||||
|
},
|
||||||
|
getSupportAlertRequests() {
|
||||||
|
return this.requests.filter((request) =>
|
||||||
|
["medevac_9line", "fire_support", "air_support"].includes(
|
||||||
|
request.type || "",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
buildSupportAlertMessage() {
|
||||||
|
const alertRequests = this.getSupportAlertRequests();
|
||||||
|
if (!alertRequests.length) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = alertRequests.map((request) => {
|
||||||
|
const groupLabel =
|
||||||
|
request.groupCallsign || request.groupId || "Unknown Group";
|
||||||
|
const typeLabel = this.getRequestTypeLabel(
|
||||||
|
request.type || "request",
|
||||||
|
);
|
||||||
|
return `${groupLabel} ${typeLabel}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return `Support request alert: ${labels.join(", ")}`;
|
||||||
|
},
|
||||||
|
getSortedGroups() {
|
||||||
|
return this.groups.slice().sort((left, right) => {
|
||||||
|
const leftDanger = (left.status || "") === "danger" ? 0 : 1;
|
||||||
|
const rightDanger = (right.status || "") === "danger" ? 0 : 1;
|
||||||
|
|
||||||
|
if (leftDanger !== rightDanger) {
|
||||||
|
return leftDanger - rightDanger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftCallsign = left.callsign || left.groupId || "";
|
||||||
|
const rightCallsign = right.callsign || right.groupId || "";
|
||||||
|
return leftCallsign.localeCompare(rightCallsign);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isDispatchOrder(entry) {
|
||||||
|
return (
|
||||||
|
!!entry.isDispatchOrder || (entry.type || "") === "dispatch_order"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
formatTypeLabel(entry) {
|
||||||
|
const typeLabel = (entry.type || "task").replaceAll("_", " ");
|
||||||
|
return this.isDispatchOrder(entry) ? "dispatch order" : typeLabel;
|
||||||
|
},
|
||||||
|
getRequestTypeLabel(typeID) {
|
||||||
|
switch (typeID) {
|
||||||
|
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 (typeID || "request").replaceAll("_", " ");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildGroupOptions(selectedGroupID) {
|
||||||
|
return this.getSortedGroups()
|
||||||
|
.map((group) => {
|
||||||
|
const groupID = group.groupId || "";
|
||||||
|
return `<option value="${groupID}" ${groupID === selectedGroupID ? "selected" : ""}>${group.callsign || groupID}</option>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
},
|
||||||
|
updateDangerAlert() {
|
||||||
|
const alertEl = document.getElementById("dispatcherDangerAlert");
|
||||||
|
if (!alertEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dangerGroups = this.getDangerGroups();
|
||||||
|
if (!dangerGroups.length) {
|
||||||
|
alertEl.textContent = "";
|
||||||
|
alertEl.classList.add("is-hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callsigns = dangerGroups.map(
|
||||||
|
(group) => group.callsign || group.groupId || "Unknown Group",
|
||||||
|
);
|
||||||
|
alertEl.textContent = `Danger alert active: ${callsigns.join(", ")}`;
|
||||||
|
alertEl.classList.remove("is-hidden");
|
||||||
|
},
|
||||||
|
updateRequestAlert() {
|
||||||
|
const alertEl = document.getElementById("dispatcherRequestAlert");
|
||||||
|
if (!alertEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const alertMessage = this.buildSupportAlertMessage();
|
||||||
|
if (!alertMessage) {
|
||||||
|
alertEl.textContent = "";
|
||||||
|
alertEl.classList.add("is-hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alertEl.textContent = alertMessage;
|
||||||
|
alertEl.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(requestID) {
|
||||||
|
const request = this.requests.find(
|
||||||
|
(entry) => entry.requestId === requestID,
|
||||||
|
);
|
||||||
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewingRequestId = requestID;
|
||||||
|
this.populateRequestModal(request);
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherRequestModal")
|
||||||
|
.classList.remove("is-hidden");
|
||||||
|
},
|
||||||
|
closeRequestModal() {
|
||||||
|
this.viewingRequestId = "";
|
||||||
|
document
|
||||||
|
.getElementById("dispatcherRequestModal")
|
||||||
|
.classList.add("is-hidden");
|
||||||
|
},
|
||||||
|
syncRequestModal() {
|
||||||
|
if (!this.viewingRequestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = this.requests.find(
|
||||||
|
(entry) => entry.requestId === this.viewingRequestId,
|
||||||
|
);
|
||||||
|
if (!request) {
|
||||||
|
this.closeRequestModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.populateRequestModal(request);
|
||||||
|
},
|
||||||
|
formatRequestFieldLabel(fieldID) {
|
||||||
|
return (fieldID || "field")
|
||||||
|
.replaceAll("_", " ")
|
||||||
|
.replace(/\b\w/g, (character) => character.toUpperCase());
|
||||||
|
},
|
||||||
|
formatRequestFieldValue(value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = String(value ?? "").trim();
|
||||||
|
return text || "Not provided";
|
||||||
|
},
|
||||||
|
populateRequestModal(request) {
|
||||||
|
const fields =
|
||||||
|
request.fields && typeof request.fields === "object"
|
||||||
|
? Object.entries(request.fields)
|
||||||
|
: [];
|
||||||
|
const fieldsHTML = fields.length
|
||||||
|
? fields
|
||||||
|
.map(
|
||||||
|
([fieldID, value]) => `
|
||||||
|
<div class="dispatch-detail-row">
|
||||||
|
<span class="dispatch-detail-label">${this.formatRequestFieldLabel(fieldID)}</span>
|
||||||
|
<span class="dispatch-detail-value">${this.formatRequestFieldValue(value)}</span>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
: '<div class="placeholder-message"><p>No submitted fields.</p></div>';
|
||||||
|
|
||||||
|
document.getElementById("dispatcherRequestTitle").textContent =
|
||||||
|
request.title || request.requestId || "Support Request";
|
||||||
|
document.getElementById("dispatcherRequestPriority").textContent = (
|
||||||
|
request.priority || "priority"
|
||||||
|
).replaceAll("_", " ");
|
||||||
|
document.getElementById("dispatcherRequestGroup").textContent =
|
||||||
|
request.groupCallsign || request.groupId || "Unknown";
|
||||||
|
document.getElementById("dispatcherRequestType").textContent =
|
||||||
|
this.getRequestTypeLabel(request.type || "request");
|
||||||
|
document.getElementById("dispatcherRequestSummary").textContent =
|
||||||
|
request.summary || "No summary provided.";
|
||||||
|
document.getElementById("dispatcherRequestFields").innerHTML =
|
||||||
|
fieldsHTML;
|
||||||
|
},
|
||||||
|
populateOrderModal(selectedAssigneeID, selectedTargetID) {
|
||||||
|
const sortedGroups = this.getSortedGroups();
|
||||||
|
const assigneeSelect = document.getElementById(
|
||||||
|
"dispatcherOrderAssigneeSelect",
|
||||||
|
);
|
||||||
|
const targetSelect = document.getElementById(
|
||||||
|
"dispatcherOrderTargetSelect",
|
||||||
|
);
|
||||||
|
if (!assigneeSelect || !targetSelect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackAssignee =
|
||||||
|
selectedAssigneeID || sortedGroups[0]?.groupId || "";
|
||||||
|
const fallbackTarget =
|
||||||
|
selectedTargetID ||
|
||||||
|
sortedGroups.find(
|
||||||
|
(group) => (group.groupId || "") !== fallbackAssignee,
|
||||||
|
)?.groupId ||
|
||||||
|
sortedGroups[0]?.groupId ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
assigneeSelect.innerHTML = this.buildGroupOptions(fallbackAssignee);
|
||||||
|
targetSelect.innerHTML = this.buildGroupOptions(fallbackTarget);
|
||||||
|
},
|
||||||
|
syncOrderModal() {
|
||||||
|
const modalEl = document.getElementById("dispatcherOrderModal");
|
||||||
|
if (!modalEl || modalEl.classList.contains("is-hidden")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.populateOrderModal(
|
||||||
|
document.getElementById("dispatcherOrderAssigneeSelect")?.value ||
|
||||||
|
"",
|
||||||
|
document.getElementById("dispatcherOrderTargetSelect")?.value || "",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
createDispatchOrder() {
|
||||||
|
const assigneeGroupID = document.getElementById(
|
||||||
|
"dispatcherOrderAssigneeSelect",
|
||||||
|
).value;
|
||||||
|
const targetGroupID = document.getElementById(
|
||||||
|
"dispatcherOrderTargetSelect",
|
||||||
|
).value;
|
||||||
|
const priority = document.getElementById(
|
||||||
|
"dispatcherOrderPrioritySelect",
|
||||||
|
).value;
|
||||||
|
const note = document.getElementById("dispatcherOrderNoteInput").value;
|
||||||
|
|
||||||
|
if (!assigneeGroupID || !targetGroupID) {
|
||||||
|
this.setStatus(
|
||||||
|
"Select both an assignee and a target group.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assigneeGroupID === targetGroupID) {
|
||||||
|
this.setStatus(
|
||||||
|
"Assignee and target groups must be different.",
|
||||||
|
"error",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus("Creating dispatch order...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::dispatchOrder::create", {
|
||||||
|
assigneeGroupID: assigneeGroupID,
|
||||||
|
targetGroupID: targetGroupID,
|
||||||
|
note: note.trim(),
|
||||||
|
priority: priority,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.closeOrderModal();
|
||||||
|
},
|
||||||
assignTask(taskID) {
|
assignTask(taskID) {
|
||||||
const selector = document.getElementById(
|
const selector = document.getElementById(
|
||||||
`dispatcher-assign-group-${taskID}`,
|
`dispatcher-assign-group-${taskID}`,
|
||||||
@ -168,32 +497,39 @@ window.cadDispatcher = {
|
|||||||
const statusValue = document.getElementById(
|
const statusValue = document.getElementById(
|
||||||
"dispatcherModalStatusSelect",
|
"dispatcherModalStatusSelect",
|
||||||
).value;
|
).value;
|
||||||
let hasChanges = false;
|
const nextRole =
|
||||||
|
roleValue && roleValue !== (group.role || "") ? roleValue : "";
|
||||||
if (roleValue && roleValue !== (group.role || "")) {
|
const nextStatus =
|
||||||
hasChanges = true;
|
statusValue && statusValue !== (group.status || "")
|
||||||
this.setStatus("Updating group role...", "info");
|
? statusValue
|
||||||
window.mapUI.sendEvent("cad::groups::role", {
|
: "";
|
||||||
groupID: this.editingGroupId,
|
const hasChanges = nextRole || nextStatus;
|
||||||
role: roleValue,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (statusValue && statusValue !== (group.status || "")) {
|
|
||||||
hasChanges = true;
|
|
||||||
this.setStatus("Updating group status...", "info");
|
|
||||||
window.mapUI.sendEvent("cad::groups::status", {
|
|
||||||
groupID: this.editingGroupId,
|
|
||||||
status: statusValue,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasChanges) {
|
if (!hasChanges) {
|
||||||
this.setStatus("No group changes to save.", "info");
|
this.setStatus("No group changes to save.", "info");
|
||||||
|
this.closeGroupModal();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setStatus("Updating group profile...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::groups::profile", {
|
||||||
|
groupID: this.editingGroupId,
|
||||||
|
role: nextRole,
|
||||||
|
status: nextStatus,
|
||||||
|
});
|
||||||
|
|
||||||
this.closeGroupModal();
|
this.closeGroupModal();
|
||||||
},
|
},
|
||||||
|
closeDispatchOrder(taskID) {
|
||||||
|
if (!taskID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus("Closing dispatch order...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::dispatchOrder::close", {
|
||||||
|
taskID: taskID,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
buildGroupEditorButton(groupID) {
|
buildGroupEditorButton(groupID) {
|
||||||
return `
|
return `
|
||||||
@ -208,6 +544,38 @@ window.cadDispatcher = {
|
|||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
buildCloseOrderButton(taskID) {
|
||||||
|
return `
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn dispatch-btn-secondary"
|
||||||
|
onclick="window.cadDispatcher.closeDispatchOrder('${taskID}')"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
buildCloseRequestButton(requestID) {
|
||||||
|
return `
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="dispatch-btn dispatch-btn-secondary"
|
||||||
|
onclick="event.stopPropagation(); window.cadDispatcher.closeSupportRequest('${requestID}')"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
closeSupportRequest(requestID) {
|
||||||
|
if (!requestID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus("Closing support request...", "info");
|
||||||
|
window.mapUI.sendEvent("cad::supportRequest::close", {
|
||||||
|
requestID: requestID,
|
||||||
|
});
|
||||||
|
},
|
||||||
renderMetrics() {
|
renderMetrics() {
|
||||||
const assignedContracts = this.contracts.filter(
|
const assignedContracts = this.contracts.filter(
|
||||||
(entry) => (entry.assignmentState || "unassigned") !== "unassigned",
|
(entry) => (entry.assignmentState || "unassigned") !== "unassigned",
|
||||||
@ -215,6 +583,8 @@ window.cadDispatcher = {
|
|||||||
const openContracts = this.contracts.filter(
|
const openContracts = this.contracts.filter(
|
||||||
(entry) => (entry.assignmentState || "unassigned") === "unassigned",
|
(entry) => (entry.assignmentState || "unassigned") === "unassigned",
|
||||||
);
|
);
|
||||||
|
const openRequests = this.requests.length;
|
||||||
|
const supportAlertRequests = this.getSupportAlertRequests();
|
||||||
const dangerGroups = this.groups.filter(
|
const dangerGroups = this.groups.filter(
|
||||||
(group) => (group.status || "") === "danger",
|
(group) => (group.status || "") === "danger",
|
||||||
);
|
);
|
||||||
@ -225,8 +595,30 @@ window.cadDispatcher = {
|
|||||||
assignedContracts.length;
|
assignedContracts.length;
|
||||||
document.getElementById("metricActiveGroups").textContent =
|
document.getElementById("metricActiveGroups").textContent =
|
||||||
this.groups.length;
|
this.groups.length;
|
||||||
|
document.getElementById("metricOpenRequests").textContent =
|
||||||
|
openRequests;
|
||||||
document.getElementById("metricDangerGroups").textContent =
|
document.getElementById("metricDangerGroups").textContent =
|
||||||
dangerGroups.length;
|
dangerGroups.length;
|
||||||
|
|
||||||
|
const dangerMetricCard = document.getElementById(
|
||||||
|
"metricDangerGroupsCard",
|
||||||
|
);
|
||||||
|
if (dangerMetricCard) {
|
||||||
|
dangerMetricCard.classList.toggle(
|
||||||
|
"is-danger",
|
||||||
|
dangerGroups.length > 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestMetricCard = document.getElementById(
|
||||||
|
"metricOpenRequestsCard",
|
||||||
|
);
|
||||||
|
if (requestMetricCard) {
|
||||||
|
requestMetricCard.classList.toggle(
|
||||||
|
"is-warning",
|
||||||
|
supportAlertRequests.length > 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
renderOpenContracts() {
|
renderOpenContracts() {
|
||||||
const container = document.getElementById("dispatcherOpenContracts");
|
const container = document.getElementById("dispatcherOpenContracts");
|
||||||
@ -240,12 +632,7 @@ window.cadDispatcher = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupOptions = this.groups
|
const groupOptions = this.buildGroupOptions("");
|
||||||
.map(
|
|
||||||
(group) =>
|
|
||||||
`<option value="${group.groupId}">${group.callsign || group.groupId}</option>`,
|
|
||||||
)
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
container.innerHTML = openContracts
|
container.innerHTML = openContracts
|
||||||
.map((task) => {
|
.map((task) => {
|
||||||
@ -253,17 +640,24 @@ window.cadDispatcher = {
|
|||||||
const position = Array.isArray(task.position)
|
const position = Array.isArray(task.position)
|
||||||
? task.position
|
? task.position
|
||||||
: [0, 0, 0];
|
: [0, 0, 0];
|
||||||
|
const targetGroup = this.groups.find(
|
||||||
|
(group) => group.groupId === (task.targetGroupId || ""),
|
||||||
|
);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<article class="dispatch-card">
|
<article class="dispatch-card">
|
||||||
<header class="dispatch-card-header">
|
<header class="dispatch-card-header">
|
||||||
<strong>${task.title || taskId}</strong>
|
<strong>${task.title || taskId}</strong>
|
||||||
<span class="dispatch-badge">${task.type || "task"}</span>
|
<span class="dispatch-badge">${this.formatTypeLabel(task)}</span>
|
||||||
</header>
|
</header>
|
||||||
<p class="dispatch-description">${task.description || ""}</p>
|
<p class="dispatch-description">${task.description || ""}</p>
|
||||||
<div class="dispatch-meta">
|
<div class="dispatch-meta">
|
||||||
<span>Unassigned</span>
|
<span>Unassigned</span>
|
||||||
<span>X: ${Math.round(position[0] || 0)} Y: ${Math.round(position[1] || 0)}</span>
|
<span>${window.mapUI.formatPosition(position)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Target: ${targetGroup ? targetGroup.callsign : task.targetGroupCallsign || "None"}</span>
|
||||||
|
<span>Priority: ${(task.priority || "priority").replaceAll("_", " ")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dispatch-actions">
|
<div class="dispatch-actions">
|
||||||
<select id="dispatcher-assign-group-${taskId}" class="dispatch-select">
|
<select id="dispatcher-assign-group-${taskId}" class="dispatch-select">
|
||||||
@ -297,6 +691,10 @@ window.cadDispatcher = {
|
|||||||
const assignedGroup = this.groups.find(
|
const assignedGroup = this.groups.find(
|
||||||
(group) => group.groupId === (task.assignedGroupId || ""),
|
(group) => group.groupId === (task.assignedGroupId || ""),
|
||||||
);
|
);
|
||||||
|
const targetGroup = this.groups.find(
|
||||||
|
(group) => group.groupId === (task.targetGroupId || ""),
|
||||||
|
);
|
||||||
|
const isDispatchOrder = this.isDispatchOrder(task);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<article class="dispatch-card">
|
<article class="dispatch-card">
|
||||||
@ -307,8 +705,13 @@ window.cadDispatcher = {
|
|||||||
<p class="dispatch-description">${task.description || ""}</p>
|
<p class="dispatch-description">${task.description || ""}</p>
|
||||||
<div class="dispatch-meta">
|
<div class="dispatch-meta">
|
||||||
<span>Group: ${assignedGroup ? assignedGroup.callsign : task.assignedGroupId || "Unknown"}</span>
|
<span>Group: ${assignedGroup ? assignedGroup.callsign : task.assignedGroupId || "Unknown"}</span>
|
||||||
<span>Type: ${task.type || "task"}</span>
|
<span>Type: ${this.formatTypeLabel(task)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Target: ${targetGroup ? targetGroup.callsign : task.targetGroupCallsign || "None"}</span>
|
||||||
|
<span>Priority: ${(task.priority || "priority").replaceAll("_", " ")}</span>
|
||||||
|
</div>
|
||||||
|
${isDispatchOrder ? `<div class="dispatch-actions dispatch-actions-split">${this.buildCloseOrderButton(taskId)}</div>` : ""}
|
||||||
</article>
|
</article>
|
||||||
`;
|
`;
|
||||||
})
|
})
|
||||||
@ -322,14 +725,16 @@ window.cadDispatcher = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.innerHTML = this.groups
|
container.innerHTML = this.getSortedGroups()
|
||||||
.map((group) => {
|
.map((group) => {
|
||||||
|
const isDanger = (group.status || "") === "danger";
|
||||||
return `
|
return `
|
||||||
<article class="dispatch-card dispatch-card-group">
|
<article class="dispatch-card dispatch-card-group ${isDanger ? "is-danger" : ""}">
|
||||||
<header class="dispatch-card-header">
|
<header class="dispatch-card-header">
|
||||||
<div class="dispatch-card-header-main">
|
<div class="dispatch-card-header-main">
|
||||||
<strong>${group.callsign || group.groupId}</strong>
|
<strong>${group.callsign || group.groupId}</strong>
|
||||||
<span class="dispatch-badge">${group.role || "group"}</span>
|
<span class="dispatch-badge">${group.role || "group"}</span>
|
||||||
|
${isDanger ? '<span class="dispatch-alert-badge">Danger</span>' : ""}
|
||||||
</div>
|
</div>
|
||||||
<div class="dispatch-card-header-actions">
|
<div class="dispatch-card-header-actions">
|
||||||
${this.buildGroupEditorButton(group.groupId)}
|
${this.buildGroupEditorButton(group.groupId)}
|
||||||
@ -350,30 +755,62 @@ window.cadDispatcher = {
|
|||||||
},
|
},
|
||||||
renderActivity() {
|
renderActivity() {
|
||||||
const container = document.getElementById("dispatcherActivity");
|
const container = document.getElementById("dispatcherActivity");
|
||||||
if (!this.activity.length) {
|
const requestsHTML = this.requests.length
|
||||||
container.innerHTML =
|
? this.requests
|
||||||
'<div class="placeholder-message"><p>No recent activity.</p></div>';
|
.map(
|
||||||
return;
|
(request) => `
|
||||||
}
|
<article class="dispatch-card dispatch-card-interactive ${["medevac_9line", "fire_support", "air_support"].includes(request.type || "") ? "is-warning" : ""}" onclick="window.cadDispatcher.openRequestModal('${request.requestId || ""}')">
|
||||||
|
<header class="dispatch-card-header">
|
||||||
|
<strong>${request.title || request.requestId || "Support Request"}</strong>
|
||||||
|
<span class="dispatch-badge">${(request.priority || "priority").replaceAll("_", " ")}</span>
|
||||||
|
</header>
|
||||||
|
<p class="dispatch-description">${request.summary || ""}</p>
|
||||||
|
<div class="dispatch-meta">
|
||||||
|
<span>Group: ${request.groupCallsign || request.groupId || "Unknown"}</span>
|
||||||
|
<span>${this.getRequestTypeLabel(request.type || "request")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-actions dispatch-actions-split">
|
||||||
|
${this.buildCloseRequestButton(request.requestId || "")}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
: '<div class="placeholder-message"><p>No active support requests.</p></div>';
|
||||||
|
|
||||||
container.innerHTML = this.activity
|
const activityHTML = this.activity.length
|
||||||
.slice()
|
? this.activity
|
||||||
.reverse()
|
.slice()
|
||||||
.slice(0, 12)
|
.reverse()
|
||||||
.map(
|
.slice(0, 8)
|
||||||
(entry) => `
|
.map(
|
||||||
<article class="dispatch-card">
|
(entry) => `
|
||||||
<header class="dispatch-card-header">
|
<article class="dispatch-card">
|
||||||
<strong>${entry.type || "activity"}</strong>
|
<header class="dispatch-card-header">
|
||||||
<span class="dispatch-badge">${Math.round(entry.timestamp || 0)}s</span>
|
<strong>${entry.type || "activity"}</strong>
|
||||||
</header>
|
<span class="dispatch-badge">${Math.round(entry.timestamp || 0)}s</span>
|
||||||
<p class="dispatch-description">${entry.message || ""}</p>
|
</header>
|
||||||
</article>
|
<p class="dispatch-description">${entry.message || ""}</p>
|
||||||
`,
|
</article>
|
||||||
)
|
`,
|
||||||
.join("");
|
)
|
||||||
|
.join("")
|
||||||
|
: '<div class="placeholder-message"><p>No recent activity.</p></div>';
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="dispatch-inline-section">
|
||||||
|
<div class="dispatch-inline-header">Support Requests</div>
|
||||||
|
${requestsHTML}
|
||||||
|
</div>
|
||||||
|
<div class="dispatch-inline-section">
|
||||||
|
<div class="dispatch-inline-header">Recent Activity</div>
|
||||||
|
${activityHTML}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
|
this.updateDangerAlert();
|
||||||
|
this.updateRequestAlert();
|
||||||
this.renderMetrics();
|
this.renderMetrics();
|
||||||
this.renderOpenContracts();
|
this.renderOpenContracts();
|
||||||
this.renderAssignedContracts();
|
this.renderAssignedContracts();
|
||||||
|
|||||||
@ -9,17 +9,22 @@ window.mapUIState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.mapUI = {
|
window.mapUI = {
|
||||||
|
formatGridCoordinate(value) {
|
||||||
|
return Math.round(Number(value) || 0)
|
||||||
|
.toString()
|
||||||
|
.padStart(4, "0");
|
||||||
|
},
|
||||||
|
formatPosition(position) {
|
||||||
|
const safePosition = Array.isArray(position) ? position : [0, 0, 0];
|
||||||
|
return `X: ${this.formatGridCoordinate(safePosition[0])} Y: ${this.formatGridCoordinate(safePosition[1])}`;
|
||||||
|
},
|
||||||
sendEvent(event, data) {
|
sendEvent(event, data) {
|
||||||
A3API.SendAlert(JSON.stringify({ event: event, data: data }));
|
A3API.SendAlert(JSON.stringify({ event: event, data: data }));
|
||||||
},
|
},
|
||||||
updateCoordinates(x, y) {
|
updateCoordinates(x, y) {
|
||||||
const coordDisplay = document.getElementById("coordsDisplay");
|
const coordDisplay = document.getElementById("coordsDisplay");
|
||||||
if (coordDisplay) {
|
if (coordDisplay) {
|
||||||
coordDisplay.textContent = `X: ${Math.round(x)
|
coordDisplay.textContent = this.formatPosition([x, y, 0]);
|
||||||
.toString()
|
|
||||||
.padStart(4, "0")} Y: ${Math.round(y)
|
|
||||||
.toString()
|
|
||||||
.padStart(4, "0")}`;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateScale(scale) {
|
updateScale(scale) {
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-content">
|
<div class="panel-content">
|
||||||
<div id="cadStatusMessage" class="task-status-message"></div>
|
<div id="cadStatusMessage" class="task-status-message"></div>
|
||||||
|
<div id="cadDangerAlert" class="cad-danger-alert is-hidden"></div>
|
||||||
|
<div id="cadRequestAlert" class="cad-warning-alert is-hidden"></div>
|
||||||
<div class="cad-tabs" role="tablist" aria-label="CAD Sections">
|
<div class="cad-tabs" role="tablist" aria-label="CAD Sections">
|
||||||
<button
|
<button
|
||||||
id="tabContractsBtn"
|
id="tabContractsBtn"
|
||||||
@ -26,6 +28,14 @@
|
|||||||
>
|
>
|
||||||
Roster
|
Roster
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
id="tabRequestsBtn"
|
||||||
|
class="cad-tab"
|
||||||
|
type="button"
|
||||||
|
data-tab="requests"
|
||||||
|
>
|
||||||
|
Requests
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
id="tabActivityBtn"
|
id="tabActivityBtn"
|
||||||
class="cad-tab"
|
class="cad-tab"
|
||||||
@ -56,6 +66,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
id="requestsPanel"
|
||||||
|
class="cad-section"
|
||||||
|
data-panel="requests"
|
||||||
|
>
|
||||||
|
<div class="cad-section-header">Support Requests</div>
|
||||||
|
<div id="requestList" class="task-list">
|
||||||
|
<div class="placeholder-message">
|
||||||
|
<p>No support requests.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
id="activityPanel"
|
id="activityPanel"
|
||||||
class="cad-section"
|
class="cad-section"
|
||||||
@ -71,6 +93,61 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="cadRequestModal" class="cad-modal is-hidden">
|
||||||
|
<div class="cad-modal-backdrop"></div>
|
||||||
|
<div
|
||||||
|
class="cad-modal-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="cadRequestModalTitle"
|
||||||
|
>
|
||||||
|
<div class="cad-modal-header">
|
||||||
|
<div>
|
||||||
|
<div class="cad-section-header">Support Request</div>
|
||||||
|
<h3 id="cadRequestModalTitle">Submit Request</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="cadRequestModalCloseBtn"
|
||||||
|
class="cad-icon-btn"
|
||||||
|
type="button"
|
||||||
|
aria-label="Close support request form"
|
||||||
|
>
|
||||||
|
x
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="cad-modal-body">
|
||||||
|
<div class="cad-modal-fields">
|
||||||
|
<label class="cad-field">
|
||||||
|
<span>Priority</span>
|
||||||
|
<select
|
||||||
|
id="cadRequestPrioritySelect"
|
||||||
|
class="cad-select"
|
||||||
|
>
|
||||||
|
<option value="routine">routine</option>
|
||||||
|
<option value="priority" selected>
|
||||||
|
priority
|
||||||
|
</option>
|
||||||
|
<option value="emergency">emergency</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
id="cadRequestFields"
|
||||||
|
class="cad-modal-fields"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cad-modal-actions">
|
||||||
|
<button
|
||||||
|
id="cadRequestModalSaveBtn"
|
||||||
|
type="button"
|
||||||
|
class="task-accept-btn"
|
||||||
|
>
|
||||||
|
Submit Request
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.MapLoader = {
|
window.MapLoader = {
|
||||||
loadCSS(path) {
|
loadCSS(path) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -81,9 +81,49 @@ body {
|
|||||||
color: #ff8a80;
|
color: #ff8a80;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-danger-alert {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.38);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(92, 18, 18, 0.94),
|
||||||
|
rgba(128, 29, 29, 0.82)
|
||||||
|
);
|
||||||
|
color: #ffd4cf;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-danger-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-warning-alert {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(246, 198, 84, 0.42);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(89, 64, 12, 0.94),
|
||||||
|
rgba(125, 92, 18, 0.84)
|
||||||
|
);
|
||||||
|
color: #ffe9b2;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-warning-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.dispatch-metrics {
|
.dispatch-metrics {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +147,28 @@ body {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metric-card.is-danger {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(74, 17, 17, 0.86),
|
||||||
|
rgba(22, 13, 16, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 107, 107, 0.12);
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-card.is-warning {
|
||||||
|
border-color: rgba(246, 198, 84, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(92, 65, 14, 0.86),
|
||||||
|
rgba(29, 22, 11, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(246, 198, 84, 0.12);
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.dispatch-grid {
|
.dispatch-grid {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -166,12 +228,35 @@ body {
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-inline-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-inline-header {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
.dispatch-card {
|
.dispatch-card {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
background: rgba(19, 26, 34, 0.72);
|
background: rgba(19, 26, 34, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-card-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card-interactive:hover {
|
||||||
|
border-color: rgba(91, 187, 255, 0.2);
|
||||||
|
background: rgba(23, 31, 40, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
.dispatch-card-header,
|
.dispatch-card-header,
|
||||||
.dispatch-meta {
|
.dispatch-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -218,6 +303,17 @@ body {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-alert-badge {
|
||||||
|
padding: 3px 7px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.44);
|
||||||
|
background: rgba(95, 23, 23, 0.88);
|
||||||
|
color: #ffd8d1;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
.dispatch-icon-btn {
|
.dispatch-icon-btn {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@ -238,6 +334,38 @@ body {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-danger {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(69, 20, 22, 0.78),
|
||||||
|
rgba(28, 17, 21, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 107, 107, 0.1);
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-danger .dispatch-meta,
|
||||||
|
.dispatch-card.is-danger .dispatch-description {
|
||||||
|
color: rgba(255, 232, 228, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-warning {
|
||||||
|
border-color: rgba(246, 198, 84, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(86, 64, 17, 0.78),
|
||||||
|
rgba(34, 27, 16, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(246, 198, 84, 0.1);
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-card.is-warning .dispatch-meta,
|
||||||
|
.dispatch-card.is-warning .dispatch-description {
|
||||||
|
color: rgba(255, 243, 214, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
.dispatch-actions-split {
|
.dispatch-actions-split {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
@ -247,6 +375,18 @@ body {
|
|||||||
padding: 9px 10px;
|
padding: 9px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 92px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(24, 31, 40, 0.92);
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.placeholder-message {
|
.placeholder-message {
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -257,6 +397,11 @@ body {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 32px 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dispatch-modal.is-hidden {
|
.dispatch-modal.is-hidden {
|
||||||
@ -271,8 +416,11 @@ body {
|
|||||||
|
|
||||||
.dispatch-modal-dialog {
|
.dispatch-modal-dialog {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: min(480px, calc(100% - 48px));
|
display: flex;
|
||||||
margin: 72px auto 0;
|
flex-direction: column;
|
||||||
|
width: min(560px, calc(100% - 48px));
|
||||||
|
max-height: calc(100vh - 64px);
|
||||||
|
margin: 0;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
background: rgba(11, 17, 24, 0.98);
|
background: rgba(11, 17, 24, 0.98);
|
||||||
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.42);
|
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.42);
|
||||||
@ -298,7 +446,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dispatch-modal-body {
|
.dispatch-modal-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dispatch-meta-grid {
|
.dispatch-meta-grid {
|
||||||
@ -337,3 +488,75 @@ body {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-block,
|
||||||
|
.dispatch-detail-list {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
background: rgba(19, 26, 34, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-block {
|
||||||
|
padding: 12px;
|
||||||
|
color: rgba(241, 246, 251, 0.82);
|
||||||
|
line-height: 1.45;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 180px) minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: rgba(14, 20, 28, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-label {
|
||||||
|
color: rgba(233, 241, 248, 0.64);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-detail-value {
|
||||||
|
color: rgba(241, 246, 251, 0.84);
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cad-danger-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 107, 107, 0.08),
|
||||||
|
0 0 0 rgba(255, 107, 107, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 141, 141, 0.22),
|
||||||
|
0 0 18px rgba(255, 107, 107, 0.16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cad-warning-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(246, 198, 84, 0.08),
|
||||||
|
0 0 0 rgba(246, 198, 84, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(251, 212, 118, 0.22),
|
||||||
|
0 0 18px rgba(246, 198, 84, 0.16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -57,19 +57,29 @@ body {
|
|||||||
|
|
||||||
.cad-tabs {
|
.cad-tabs {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 6px;
|
gap: 5px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cad-tabs.is-two-col {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-tabs.is-three-col {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.cad-tab {
|
.cad-tab {
|
||||||
padding: 8px 10px;
|
min-width: 0;
|
||||||
|
padding: 8px 7px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
background: rgba(20, 27, 33, 0.88);
|
background: rgba(20, 27, 33, 0.88);
|
||||||
color: rgba(243, 246, 249, 0.78);
|
color: rgba(243, 246, 249, 0.78);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
font-size: 11px;
|
font-size: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,12 +156,162 @@ body {
|
|||||||
color: #ff8a80;
|
color: #ff8a80;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cad-modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(4, 8, 12, 0.76);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-dialog {
|
||||||
|
position: relative;
|
||||||
|
width: min(480px, calc(100% - 28px));
|
||||||
|
margin: 32px auto 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(11, 17, 24, 0.98);
|
||||||
|
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-header,
|
||||||
|
.cad-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-header {
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-header h3 {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-body {
|
||||||
|
padding: 14px;
|
||||||
|
max-height: 62vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-fields {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-field {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-field span {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: rgba(233, 241, 248, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-input,
|
||||||
|
.cad-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
background: rgba(30, 37, 43, 0.9);
|
||||||
|
color: #f3f6f9;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-textarea {
|
||||||
|
min-height: 74px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-icon-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
background: rgba(24, 31, 40, 0.92);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-modal-actions {
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-danger-alert {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.36);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(92, 18, 18, 0.94),
|
||||||
|
rgba(128, 29, 29, 0.82)
|
||||||
|
);
|
||||||
|
color: #ffd4cf;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-danger-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-warning-alert {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(246, 198, 84, 0.4);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(89, 64, 12, 0.94),
|
||||||
|
rgba(125, 92, 18, 0.84)
|
||||||
|
);
|
||||||
|
color: #ffe9b2;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
animation: cad-warning-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-warning-alert.is-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.task-list {
|
.task-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cad-request-actions {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cad-request-btn {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.task-action-stack,
|
.task-action-stack,
|
||||||
.task-action-row {
|
.task-action-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -169,6 +329,18 @@ body {
|
|||||||
background: rgba(12, 16, 20, 0.62);
|
background: rgba(12, 16, 20, 0.62);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-card.is-danger,
|
||||||
|
.roster-summary-card.is-danger {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(69, 20, 22, 0.78),
|
||||||
|
rgba(28, 17, 21, 0.92)
|
||||||
|
);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 107, 107, 0.1);
|
||||||
|
animation: cad-danger-pulse 1.35s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.task-card-header {
|
.task-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -207,10 +379,137 @@ body {
|
|||||||
background: rgba(16, 23, 29, 0.82);
|
background: rgba(16, 23, 29, 0.82);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-alert-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border: 1px solid rgba(255, 107, 107, 0.44);
|
||||||
|
background: rgba(95, 23, 23, 0.88);
|
||||||
|
color: #ffd8d1;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.roster-member-card {
|
.roster-member-card {
|
||||||
background: rgba(12, 16, 20, 0.74);
|
background: rgba(12, 16, 20, 0.74);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
border-color 120ms ease,
|
||||||
|
background 120ms ease,
|
||||||
|
transform 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card .task-type {
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card .task-meta {
|
||||||
|
color: var(--muted);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card:hover {
|
||||||
|
border-color: rgba(91, 187, 255, 0.26);
|
||||||
|
background: rgba(18, 29, 38, 0.9);
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card.is-selected {
|
||||||
|
border-color: rgba(91, 187, 255, 0.52);
|
||||||
|
background: rgba(15, 40, 58, 0.92);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(91, 187, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card.is-danger:not(.is-selected) {
|
||||||
|
border-color: rgba(255, 107, 107, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(69, 20, 22, 0.78),
|
||||||
|
rgba(28, 17, 21, 0.92)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-group-card.is-danger .task-meta,
|
||||||
|
.roster-summary-card.is-danger .task-meta {
|
||||||
|
color: rgba(255, 232, 228, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border-radius: 0;
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
border-color 120ms ease,
|
||||||
|
background 120ms ease,
|
||||||
|
transform 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card .task-type {
|
||||||
|
color: var(--accent);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card .task-description {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card .task-meta {
|
||||||
|
color: var(--muted);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card:hover {
|
||||||
|
border-color: rgba(91, 187, 255, 0.26);
|
||||||
|
background: rgba(18, 29, 38, 0.9);
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card.is-selected {
|
||||||
|
border-color: rgba(91, 187, 255, 0.52);
|
||||||
|
background: rgba(15, 40, 58, 0.92);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(91, 187, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card.is-warning:not(.is-selected) {
|
||||||
|
border-color: rgba(246, 198, 84, 0.34);
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(86, 64, 17, 0.78),
|
||||||
|
rgba(34, 27, 16, 0.92)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatch-map-card.is-warning .task-meta,
|
||||||
|
.dispatch-map-card.is-warning .task-description {
|
||||||
|
color: rgba(255, 243, 214, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
.roster-leader-badge {
|
.roster-leader-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -223,3 +522,33 @@ body {
|
|||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes cad-danger-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 107, 107, 0.08),
|
||||||
|
0 0 0 rgba(255, 107, 107, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(255, 141, 141, 0.22),
|
||||||
|
0 0 14px rgba(255, 107, 107, 0.14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cad-warning-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(246, 198, 84, 0.08),
|
||||||
|
0 0 0 rgba(246, 198, 84, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(251, 212, 118, 0.22),
|
||||||
|
0 0 18px rgba(246, 198, 84, 0.16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -71,11 +71,7 @@ window.cadTopbar = {
|
|||||||
const position = Array.isArray(group?.position)
|
const position = Array.isArray(group?.position)
|
||||||
? group.position
|
? group.position
|
||||||
: [0, 0, 0];
|
: [0, 0, 0];
|
||||||
return `X: ${Math.round(position[0] || 0)
|
return window.mapUI.formatPosition(position);
|
||||||
.toString()
|
|
||||||
.padStart(4, "0")} Y: ${Math.round(position[1] || 0)
|
|
||||||
.toString()
|
|
||||||
.padStart(4, "0")}`;
|
|
||||||
},
|
},
|
||||||
receiveState(payload) {
|
receiveState(payload) {
|
||||||
this.session =
|
this.session =
|
||||||
|
|||||||
@ -3,3 +3,5 @@ PREP(initAssignmentRepository);
|
|||||||
PREP(initCadStore);
|
PREP(initCadStore);
|
||||||
PREP(initGroupRepository);
|
PREP(initGroupRepository);
|
||||||
PREP(initPermissionService);
|
PREP(initPermissionService);
|
||||||
|
PREP(initPersistenceService);
|
||||||
|
PREP(initRequestRepository);
|
||||||
|
|||||||
@ -9,11 +9,7 @@ call FUNC(initCadStore);
|
|||||||
[QGVAR(requestHydrateCad), {
|
[QGVAR(requestHydrateCad), {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "") exitWith {
|
private _player = GVAR(CadStore) call ["resolveRequestPlayer", [_uid, "CAD hydrate request received with empty UID."]];
|
||||||
["WARNING", "CAD hydrate request received with empty UID."] call EFUNC(common,log);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
if (_player isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
private _payload = GVAR(CadStore) call ["buildHydratePayload", [_uid]];
|
private _payload = GVAR(CadStore) call ["buildHydratePayload", [_uid]];
|
||||||
@ -28,74 +24,195 @@ call FUNC(initCadStore);
|
|||||||
["_note", "", [""]]
|
["_note", "", [""]]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _taskID isEqualTo "" } || { _groupID isEqualTo "" }) exitWith {
|
if (_taskID isEqualTo "" || { _groupID isEqualTo "" }) exitWith {
|
||||||
["WARNING", "Invalid CAD task assignment payload."] call EFUNC(common,log);
|
["WARNING", "Invalid CAD task assignment payload."] call EFUNC(common,log);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
_uid,
|
||||||
|
"Invalid CAD task assignment payload.",
|
||||||
|
CRPC(cad,responseCadAssignment),
|
||||||
|
"assignTaskToGroup",
|
||||||
|
[_uid, _taskID, _groupID, _note],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
private _result = GVAR(CadStore) call ["assignTaskToGroup", [_uid, _taskID, _groupID, _note]];
|
[QGVAR(requestCreateCadDispatchOrder), {
|
||||||
[CRPC(cad,responseCadAssignment), [_result], _player] call CFUNC(targetEvent);
|
params [
|
||||||
[CRPC(cad,responseHydrateCad), [GVAR(CadStore) call ["buildHydratePayload", [_uid]]], _player] call CFUNC(targetEvent);
|
["_uid", "", [""]],
|
||||||
|
["_assigneeGroupID", "", [""]],
|
||||||
|
["_targetGroupID", "", [""]],
|
||||||
|
["_note", "", [""]],
|
||||||
|
["_priority", "priority", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_assigneeGroupID isEqualTo "" || { _targetGroupID isEqualTo "" }) exitWith {
|
||||||
|
["WARNING", "Invalid CAD dispatch order payload."] call EFUNC(common,log);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
|
_uid,
|
||||||
|
"Invalid CAD dispatch order payload.",
|
||||||
|
CRPC(cad,responseCadAssignment),
|
||||||
|
"createDispatchOrder",
|
||||||
|
[_uid, _assigneeGroupID, _targetGroupID, _note, _priority],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestSubmitCadSupportRequest), {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_type", "", [""]],
|
||||||
|
["_fields", createHashMap, [createHashMap]],
|
||||||
|
["_priority", "priority", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_type isEqualTo "") exitWith {
|
||||||
|
["WARNING", "Invalid CAD support request payload."] call EFUNC(common,log);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
|
_uid,
|
||||||
|
"Invalid CAD support request payload.",
|
||||||
|
CRPC(cad,responseCadRequest),
|
||||||
|
"submitSupportRequest",
|
||||||
|
[_uid, _type, _fields, _priority],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestCloseCadSupportRequest), {
|
||||||
|
params [["_uid", "", [""]], ["_requestID", "", [""]]];
|
||||||
|
|
||||||
|
if (_requestID isEqualTo "") exitWith {
|
||||||
|
["WARNING", "Invalid CAD support request close payload."] call EFUNC(common,log);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
|
_uid,
|
||||||
|
"Invalid CAD support request close payload.",
|
||||||
|
CRPC(cad,responseCadRequest),
|
||||||
|
"closeSupportRequest",
|
||||||
|
[_uid, _requestID],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestAcknowledgeCadTask), {
|
[QGVAR(requestAcknowledgeCadTask), {
|
||||||
params [["_uid", "", [""]], ["_taskID", "", [""]]];
|
params [["_uid", "", [""]], ["_taskID", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _taskID isEqualTo "" }) exitWith {
|
if (_taskID isEqualTo "") exitWith {
|
||||||
["WARNING", "Invalid CAD acknowledge payload."] call EFUNC(common,log);
|
["WARNING", "Invalid CAD acknowledge payload."] call EFUNC(common,log);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
_uid,
|
||||||
|
"Invalid CAD acknowledge payload.",
|
||||||
|
CRPC(cad,responseCadAssignment),
|
||||||
|
"acknowledgeTask",
|
||||||
|
[_uid, _taskID],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
private _result = GVAR(CadStore) call ["acknowledgeTask", [_uid, _taskID]];
|
[QGVAR(requestCloseCadDispatchOrder), {
|
||||||
[CRPC(cad,responseCadAssignment), [_result], _player] call CFUNC(targetEvent);
|
params [["_uid", "", [""]], ["_taskID", "", [""]]];
|
||||||
[CRPC(cad,responseHydrateCad), [GVAR(CadStore) call ["buildHydratePayload", [_uid]]], _player] call CFUNC(targetEvent);
|
|
||||||
|
if (_taskID isEqualTo "") exitWith {
|
||||||
|
["WARNING", "Invalid CAD dispatch order close payload."] call EFUNC(common,log);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
|
_uid,
|
||||||
|
"Invalid CAD dispatch order close payload.",
|
||||||
|
CRPC(cad,responseCadAssignment),
|
||||||
|
"closeDispatchOrder",
|
||||||
|
[_uid, _taskID],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestDeclineCadTask), {
|
[QGVAR(requestDeclineCadTask), {
|
||||||
params [["_uid", "", [""]], ["_taskID", "", [""]]];
|
params [["_uid", "", [""]], ["_taskID", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _taskID isEqualTo "" }) exitWith {
|
if (_taskID isEqualTo "") exitWith {
|
||||||
["WARNING", "Invalid CAD decline payload."] call EFUNC(common,log);
|
["WARNING", "Invalid CAD decline payload."] call EFUNC(common,log);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
_uid,
|
||||||
|
"Invalid CAD decline payload.",
|
||||||
private _result = GVAR(CadStore) call ["declineTask", [_uid, _taskID]];
|
CRPC(cad,responseCadAssignment),
|
||||||
[CRPC(cad,responseCadAssignment), [_result], _player] call CFUNC(targetEvent);
|
"declineTask",
|
||||||
[CRPC(cad,responseHydrateCad), [GVAR(CadStore) call ["buildHydratePayload", [_uid]]], _player] call CFUNC(targetEvent);
|
[_uid, _taskID],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestUpdateCadGroupStatus), {
|
[QGVAR(requestUpdateCadGroupStatus), {
|
||||||
params [["_uid", "", [""]], ["_groupID", "", [""]], ["_status", "", [""]]];
|
params [["_uid", "", [""]], ["_groupID", "", [""]], ["_status", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _groupID isEqualTo "" } || { _status isEqualTo "" }) exitWith {
|
if (_groupID isEqualTo "" || { _status isEqualTo "" }) exitWith {
|
||||||
["WARNING", "Invalid CAD group status payload."] call EFUNC(common,log);
|
["WARNING", "Invalid CAD group status payload."] call EFUNC(common,log);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
_uid,
|
||||||
|
"Invalid CAD group status payload.",
|
||||||
private _result = GVAR(CadStore) call ["updateGroupStatus", [_uid, _groupID, _status]];
|
CRPC(cad,responseCadGroupUpdate),
|
||||||
[CRPC(cad,responseCadGroupUpdate), [_result], _player] call CFUNC(targetEvent);
|
"updateGroupStatus",
|
||||||
[CRPC(cad,responseHydrateCad), [GVAR(CadStore) call ["buildHydratePayload", [_uid]]], _player] call CFUNC(targetEvent);
|
[_uid, _groupID, _status],
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestUpdateCadGroupRole), {
|
[QGVAR(requestUpdateCadGroupRole), {
|
||||||
params [["_uid", "", [""]], ["_groupID", "", [""]], ["_role", "", [""]]];
|
params [["_uid", "", [""]], ["_groupID", "", [""]], ["_role", "", [""]]];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _groupID isEqualTo "" } || { _role isEqualTo "" }) exitWith {
|
if (_groupID isEqualTo "" || { _role isEqualTo "" }) exitWith {
|
||||||
["WARNING", "Invalid CAD group role payload."] call EFUNC(common,log);
|
["WARNING", "Invalid CAD group role payload."] call EFUNC(common,log);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
_uid,
|
||||||
|
"Invalid CAD group role payload.",
|
||||||
private _result = GVAR(CadStore) call ["updateGroupRole", [_uid, _groupID, _role]];
|
CRPC(cad,responseCadGroupUpdate),
|
||||||
[CRPC(cad,responseCadGroupUpdate), [_result], _player] call CFUNC(targetEvent);
|
"updateGroupRole",
|
||||||
[CRPC(cad,responseHydrateCad), [GVAR(CadStore) call ["buildHydratePayload", [_uid]]], _player] call CFUNC(targetEvent);
|
[_uid, _groupID, _role],
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestUpdateCadGroupProfile), {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_groupID", "", [""]],
|
||||||
|
["_status", "", [""]],
|
||||||
|
["_role", "", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_groupID isEqualTo "") exitWith {
|
||||||
|
["WARNING", "Invalid CAD group profile payload."] call EFUNC(common,log);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(CadStore) call ["dispatchRpcMutation", [
|
||||||
|
_uid,
|
||||||
|
"Invalid CAD group profile payload.",
|
||||||
|
CRPC(cad,responseCadGroupUpdate),
|
||||||
|
"updateGroupProfile",
|
||||||
|
[_uid, _groupID, _status, _role],
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|||||||
@ -24,6 +24,46 @@ GVAR(ActivityRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["#type", "CadActivityRepositoryBaseClass"],
|
["#type", "CadActivityRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["activityRegistry", []];
|
_self set ["activityRegistry", []];
|
||||||
|
_self set ["persistenceLoaded", false];
|
||||||
|
}],
|
||||||
|
["restorePersistedActivity", compileFinal {
|
||||||
|
if (_self getOrDefault ["persistenceLoaded", false]) exitWith { true };
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
|
private _result = _persistenceService call ["loadActivity", []];
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith { false };
|
||||||
|
|
||||||
|
_self set ["activityRegistry", +(_result getOrDefault ["data", []])];
|
||||||
|
_self set ["persistenceLoaded", true];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["appendEntry", compileFinal {
|
||||||
|
params [["_entry", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_entry isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
|
_self call ["restorePersistedActivity", []];
|
||||||
|
|
||||||
|
private _activityRegistry = +(_self getOrDefault ["activityRegistry", []]);
|
||||||
|
private _finalEntry = +_entry;
|
||||||
|
if ((_finalEntry getOrDefault ["timestamp", -1]) < 0) then {
|
||||||
|
_finalEntry set ["timestamp", serverTime];
|
||||||
|
};
|
||||||
|
|
||||||
|
_activityRegistry pushBack _finalEntry;
|
||||||
|
|
||||||
|
if ((count _activityRegistry) > 50) then {
|
||||||
|
_activityRegistry deleteRange [0, (count _activityRegistry) - 50];
|
||||||
|
};
|
||||||
|
|
||||||
|
_self set ["activityRegistry", _activityRegistry];
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isNotEqualTo createHashMap) then {
|
||||||
|
_persistenceService call ["appendActivity", [_finalEntry]];
|
||||||
|
};
|
||||||
|
true
|
||||||
}],
|
}],
|
||||||
["appendActivity", compileFinal {
|
["appendActivity", compileFinal {
|
||||||
params [
|
params [
|
||||||
@ -35,25 +75,17 @@ GVAR(ActivityRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (_type isEqualTo "" || { _message isEqualTo "" }) exitWith { false };
|
if (_type isEqualTo "" || { _message isEqualTo "" }) exitWith { false };
|
||||||
|
private _entry = createHashMapFromArray [
|
||||||
private _activityRegistry = +(_self getOrDefault ["activityRegistry", []]);
|
|
||||||
_activityRegistry pushBack createHashMapFromArray [
|
|
||||||
["type", _type],
|
["type", _type],
|
||||||
["message", _message],
|
["message", _message],
|
||||||
["timestamp", serverTime],
|
|
||||||
["taskId", _taskID],
|
["taskId", _taskID],
|
||||||
["groupId", _groupID],
|
["groupId", _groupID],
|
||||||
["actorUid", _actorUid]
|
["actorUid", _actorUid]
|
||||||
];
|
];
|
||||||
|
_self call ["appendEntry", [_entry]]
|
||||||
if ((count _activityRegistry) > 50) then {
|
|
||||||
_activityRegistry deleteRange [0, (count _activityRegistry) - 50];
|
|
||||||
};
|
|
||||||
|
|
||||||
_self set ["activityRegistry", _activityRegistry];
|
|
||||||
true
|
|
||||||
}],
|
}],
|
||||||
["getActivity", compileFinal {
|
["getActivity", compileFinal {
|
||||||
|
_self call ["restorePersistedActivity", []];
|
||||||
+(_self getOrDefault ["activityRegistry", []])
|
+(_self getOrDefault ["activityRegistry", []])
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|||||||
@ -25,12 +25,21 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["#type", "CadAssignmentRepositoryBaseClass"],
|
["#type", "CadAssignmentRepositoryBaseClass"],
|
||||||
["#create", compileFinal {
|
["#create", compileFinal {
|
||||||
_self set ["assignmentRegistry", createHashMap];
|
_self set ["assignmentRegistry", createHashMap];
|
||||||
|
_self set ["dispatchOrderRegistry", createHashMap];
|
||||||
|
_self set ["persistenceLoaded", false];
|
||||||
}],
|
}],
|
||||||
["pruneAssignments", compileFinal {
|
["pruneAssignments", compileFinal {
|
||||||
|
_self call ["restorePersistedState", []];
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
||||||
|
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
||||||
private _keysToRemove = [];
|
private _keysToRemove = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
|
if ((_dispatchOrderRegistry getOrDefault [_x, createHashMap]) isNotEqualTo createHashMap) then {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
private _status = EGVAR(task,TaskStore) call ["getTaskStatus", [_x]];
|
private _status = EGVAR(task,TaskStore) call ["getTaskStatus", [_x]];
|
||||||
if !(_status in ["active", ""]) then {
|
if !(_status in ["active", ""]) then {
|
||||||
_keysToRemove pushBack _x;
|
_keysToRemove pushBack _x;
|
||||||
@ -42,43 +51,90 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
} forEach _keysToRemove;
|
} forEach _keysToRemove;
|
||||||
|
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
_self set ["assignmentRegistry", _assignmentRegistry];
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isNotEqualTo createHashMap) then {
|
||||||
|
{
|
||||||
|
_persistenceService call ["deleteAssignment", [_x]];
|
||||||
|
} forEach _keysToRemove;
|
||||||
|
};
|
||||||
|
|
||||||
count _keysToRemove
|
count _keysToRemove
|
||||||
}],
|
}],
|
||||||
["getAssignments", compileFinal {
|
["getAssignments", compileFinal {
|
||||||
|
_self call ["restorePersistedState", []];
|
||||||
values (_self getOrDefault ["assignmentRegistry", createHashMap])
|
values (_self getOrDefault ["assignmentRegistry", createHashMap])
|
||||||
}],
|
}],
|
||||||
["buildContracts", compileFinal {
|
["isDispatchOrder", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_taskID", "", [""]]];
|
||||||
|
|
||||||
_self call ["pruneAssignments", []];
|
if (_taskID isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
((_self getOrDefault ["dispatchOrderRegistry", createHashMap]) getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap
|
||||||
private _contracts = [];
|
}],
|
||||||
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
["restorePersistedState", compileFinal {
|
||||||
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
|
if (_self getOrDefault ["persistenceLoaded", false]) exitWith { true };
|
||||||
private _canDispatch = _permissionService call ["canDispatch", [_uid]];
|
|
||||||
private _playerGroupId = _groupRepository call ["getPlayerGroupId", [_uid]];
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
|
private _assignmentsResult = _persistenceService call ["loadAssignments", []];
|
||||||
|
if !(_assignmentsResult getOrDefault ["success", false]) exitWith { false };
|
||||||
|
|
||||||
|
private _ordersResult = _persistenceService call ["loadDispatchOrders", []];
|
||||||
|
if !(_ordersResult getOrDefault ["success", false]) exitWith { false };
|
||||||
|
|
||||||
|
private _assignmentRegistry = +(_assignmentsResult getOrDefault ["data", createHashMap]);
|
||||||
|
private _dispatchOrderRegistry = +(_ordersResult getOrDefault ["data", createHashMap]);
|
||||||
|
|
||||||
|
_self set ["assignmentRegistry", _assignmentRegistry];
|
||||||
|
_self set ["dispatchOrderRegistry", _dispatchOrderRegistry];
|
||||||
|
_self set ["persistenceLoaded", true];
|
||||||
|
|
||||||
{
|
{
|
||||||
private _taskID = _x getOrDefault ["taskID", ""];
|
if ((_y getOrDefault ["state", ""]) isNotEqualTo "acknowledged") then { continue; };
|
||||||
if (_taskID isEqualTo "") then { continue; };
|
if (((_y getOrDefault ["acknowledgedByUid", ""]) isEqualTo "")) then { continue; };
|
||||||
|
if ((_dispatchOrderRegistry getOrDefault [_x, createHashMap]) isNotEqualTo createHashMap) then { continue; };
|
||||||
|
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) isNotEqualTo "active") then { continue; };
|
||||||
|
EGVAR(task,TaskStore) call ["bindTaskOwnership", [_x, _y getOrDefault ["acknowledgedByUid", ""]]];
|
||||||
|
} forEach _assignmentRegistry;
|
||||||
|
|
||||||
private _assignment = _assignmentRegistry getOrDefault [_taskID, createHashMap];
|
true
|
||||||
private _entry = +_x;
|
}],
|
||||||
_entry set ["taskId", _taskID];
|
["buildDispatchOrderEntry", compileFinal {
|
||||||
_entry set ["assignedGroupId", _assignment getOrDefault ["groupId", ""]];
|
params [
|
||||||
_entry set ["assignmentState", [_assignment getOrDefault ["state", ""], "unassigned"] select (_assignment isEqualTo createHashMap)];
|
["_taskID", "", [""]],
|
||||||
|
["_order", createHashMap, [createHashMap]],
|
||||||
|
["_assignmentRegistry", createHashMap, [createHashMap]],
|
||||||
|
["_groupRepository", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
if (!_canDispatch) then {
|
if (_taskID isEqualTo "" || { _order isEqualTo createHashMap }) exitWith { createHashMap };
|
||||||
private _assignedGroupId = _entry getOrDefault ["assignedGroupId", ""];
|
|
||||||
if (_assignedGroupId isEqualTo "") then { continue; };
|
private _entry = +_order;
|
||||||
if (_assignedGroupId isNotEqualTo _playerGroupId) then { continue; };
|
private _targetGroupID = _order getOrDefault ["targetGroupId", ""];
|
||||||
|
if (_targetGroupID isNotEqualTo "") then {
|
||||||
|
private _targetGroup = _groupRepository call ["getGroupRecord", [_targetGroupID]];
|
||||||
|
if (_targetGroup isNotEqualTo createHashMap) then {
|
||||||
|
private _targetCallsign = _targetGroup getOrDefault ["callsign", _targetGroupID];
|
||||||
|
_entry set ["targetGroupCallsign", _targetCallsign];
|
||||||
|
_entry set ["position", +(_targetGroup getOrDefault ["position", _entry getOrDefault ["position", []]])];
|
||||||
|
_entry set ["title", format ["Backup %1", _targetCallsign]];
|
||||||
|
|
||||||
|
if ((_order getOrDefault ["note", ""]) isEqualTo "") then {
|
||||||
|
_entry set ["description", format ["Dispatch order to back up %1 at its current position.", _targetCallsign]];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
_contracts pushBack _entry;
|
private _assignment = _assignmentRegistry getOrDefault [_taskID, createHashMap];
|
||||||
} forEach (EGVAR(task,TaskStore) call ["getActiveTaskCatalog", []]);
|
_entry set ["taskId", _taskID];
|
||||||
|
_entry set ["taskID", _taskID];
|
||||||
_contracts
|
_entry set ["type", _entry getOrDefault ["type", "dispatch_order"]];
|
||||||
|
_entry set ["isDispatchOrder", true];
|
||||||
|
_entry set ["assignedGroupId", _assignment getOrDefault ["groupId", ""]];
|
||||||
|
_entry set ["assignmentState", [_assignment getOrDefault ["state", ""], "unassigned"] select (_assignment isEqualTo createHashMap)];
|
||||||
|
_entry
|
||||||
}],
|
}],
|
||||||
["assignTaskToGroup", compileFinal {
|
["assignTaskToGroup", compileFinal {
|
||||||
params [
|
params [
|
||||||
@ -94,24 +150,29 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["assignment", createHashMap]
|
["assignment", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
_self call ["restorePersistedState", []];
|
||||||
|
|
||||||
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
||||||
_result set ["message", "You are not authorized to assign contracts."];
|
_result set ["message", "You are not authorized to assign contracts."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_taskID]]) isNotEqualTo "active") exitWith {
|
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
||||||
|
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
||||||
|
private _isDispatchOrder = (_dispatchOrderRegistry getOrDefault [_taskID, createHashMap]) isNotEqualTo createHashMap;
|
||||||
|
|
||||||
|
if (!_isDispatchOrder && { (EGVAR(task,TaskStore) call ["getTaskStatus", [_taskID]]) isNotEqualTo "active" }) exitWith {
|
||||||
_result set ["message", "Task is no longer active."];
|
_result set ["message", "Task is no longer active."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
|
||||||
private _existingAssignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
private _existingAssignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
||||||
if (
|
if (
|
||||||
_existingAssignment isNotEqualTo createHashMap
|
_existingAssignment isNotEqualTo createHashMap
|
||||||
&& { (_existingAssignment getOrDefault ["state", ""]) in ["assigned", "acknowledged"] }
|
&& { (_existingAssignment getOrDefault ["state", ""]) in ["assigned", "acknowledged"] }
|
||||||
) exitWith {
|
) exitWith {
|
||||||
_result set ["message", "Task is already assigned and must be declined or completed before reassignment."];
|
_result set ["message", ["Task is already assigned and must be declined or completed before reassignment.", "Dispatch order is already assigned and must be declined or closed before reassignment."] select _isDispatchOrder];
|
||||||
_result set ["assignment", _existingAssignment];
|
_result set ["assignment", _existingAssignment];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
@ -133,6 +194,7 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _assignment = createHashMapFromArray [
|
private _assignment = createHashMapFromArray [
|
||||||
["taskId", _taskID],
|
["taskId", _taskID],
|
||||||
["groupId", _groupID],
|
["groupId", _groupID],
|
||||||
|
["groupCallsign", _groupRecord getOrDefault ["callsign", _groupID]],
|
||||||
["assignedByUid", _requesterUid],
|
["assignedByUid", _requesterUid],
|
||||||
["assignedByName", ["Dispatcher", name _requesterPlayer] select (_requesterPlayer isNotEqualTo objNull)],
|
["assignedByName", ["Dispatcher", name _requesterPlayer] select (_requesterPlayer isNotEqualTo objNull)],
|
||||||
["assignedAt", serverTime],
|
["assignedAt", serverTime],
|
||||||
@ -140,114 +202,341 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["note", _note]
|
["note", _note]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _assignResult = _persistenceService call ["assignAssignment", [_taskID, _assignment]];
|
||||||
|
if !(_assignResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension rejected the assignment."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _assignData = +(_assignResult getOrDefault ["data", createHashMap]);
|
||||||
|
_assignment = +(_assignData getOrDefault ["assignment", createHashMap]);
|
||||||
|
if (_assignment isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension returned an invalid assignment."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
_assignmentRegistry set [_taskID, _assignment];
|
_assignmentRegistry set [_taskID, _assignment];
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
_self set ["assignmentRegistry", _assignmentRegistry];
|
||||||
|
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityEntry = +(_assignData getOrDefault ["activity", createHashMap]);
|
||||||
_activityRepository call ["appendActivity", [
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
"task_assigned",
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
format ["%1 assigned %2 to %3.", _assignment get "assignedByName", _taskID, _groupRecord getOrDefault ["callsign", _groupID]],
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
||||||
_taskID,
|
};
|
||||||
_groupID,
|
|
||||||
_requesterUid
|
|
||||||
]];
|
|
||||||
|
|
||||||
_result set ["success", true];
|
_result set ["success", true];
|
||||||
_result set ["message", "Task assigned."];
|
_result set ["message", _assignData getOrDefault ["message", ["Task assigned.", "Dispatch order assigned."] select _isDispatchOrder]];
|
||||||
_result set ["assignment", _assignment];
|
_result set ["assignment", _assignment];
|
||||||
_result set ["leaderUid", _leaderUid];
|
_result set ["leaderUid", _leaderUid];
|
||||||
|
_result set ["isDispatchOrder", _isDispatchOrder];
|
||||||
_result
|
_result
|
||||||
}],
|
}],
|
||||||
["acknowledgeTask", compileFinal {
|
["createDispatchOrder", compileFinal {
|
||||||
params [["_requesterUid", "", [""]], ["_taskID", "", [""]]];
|
params [
|
||||||
|
["_requesterUid", "", [""]],
|
||||||
|
["_assigneeGroupID", "", [""]],
|
||||||
|
["_targetGroupID", "", [""]],
|
||||||
|
["_note", "", [""]],
|
||||||
|
["_priority", "priority", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
private _result = createHashMapFromArray [
|
private _result = createHashMapFromArray [
|
||||||
["success", false],
|
["success", false],
|
||||||
["message", "Unable to acknowledge task."],
|
["message", "Unable to create dispatch order."],
|
||||||
["assignment", createHashMap]
|
["assignment", createHashMap],
|
||||||
|
["order", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
_self call ["restorePersistedState", []];
|
||||||
private _assignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
|
||||||
if (_assignment isEqualTo createHashMap) exitWith {
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
_result set ["message", "Task is not assigned."];
|
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
||||||
|
_result set ["message", "You are not authorized to create dispatch orders."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_assigneeGroupID isEqualTo "" || { _targetGroupID isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "Assignee and target groups are required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_assigneeGroupID isEqualTo _targetGroupID) exitWith {
|
||||||
|
_result set ["message", "Assignee and target groups must be different."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _groupID = _assignment getOrDefault ["groupId", ""];
|
|
||||||
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
|
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
|
||||||
if !(_groupRepository call ["isGroupLeader", [_requesterUid, _groupID]]) exitWith {
|
private _assigneeGroup = _groupRepository call ["getGroupRecord", [_assigneeGroupID]];
|
||||||
_result set ["message", "Only the assigned group leader can acknowledge this task."];
|
if (_assigneeGroup isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Selected assignee group is unavailable."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _bindResult = EGVAR(task,TaskStore) call ["bindTaskOwnership", [_taskID, _requesterUid]];
|
private _assigneeLeaderUid = _assigneeGroup getOrDefault ["leaderUid", ""];
|
||||||
if !(_bindResult getOrDefault ["success", false]) exitWith {
|
if (_assigneeLeaderUid isEqualTo "") exitWith {
|
||||||
_result set ["message", _bindResult getOrDefault ["message", "Failed to bind task ownership."]];
|
_result set ["message", "Selected assignee group has no online leader."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
_assignment set ["state", "acknowledged"];
|
private _targetGroup = _groupRepository call ["getGroupRecord", [_targetGroupID]];
|
||||||
_assignment set ["acknowledgedAt", serverTime];
|
if (_targetGroup isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Selected target group is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _validPriorities = ["routine", "priority", "emergency"];
|
||||||
|
private _finalPriority = toLowerANSI _priority;
|
||||||
|
if !(_finalPriority in _validPriorities) then {
|
||||||
|
_finalPriority = "priority";
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _dispatchContext = createHashMapFromArray [
|
||||||
|
["assigneeGroupId", _assigneeGroupID],
|
||||||
|
["assigneeGroupCallsign", _assigneeGroup getOrDefault ["callsign", _assigneeGroupID]],
|
||||||
|
["targetGroupId", _targetGroupID],
|
||||||
|
["targetGroupCallsign", _targetGroup getOrDefault ["callsign", _targetGroupID]],
|
||||||
|
["targetPosition", +(_targetGroup getOrDefault ["position", []])],
|
||||||
|
["createdByUid", _requesterUid],
|
||||||
|
["createdByName", ["Dispatcher", name _requesterPlayer] select (_requesterPlayer isNotEqualTo objNull)],
|
||||||
|
["note", _note],
|
||||||
|
["priority", _finalPriority],
|
||||||
|
["createdAt", serverTime]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _createResult = _persistenceService call ["createDispatchOrderFromContext", [_dispatchContext]];
|
||||||
|
if !(_createResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension rejected the dispatch order."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _createData = +(_createResult getOrDefault ["data", createHashMap]);
|
||||||
|
private _taskID = _createData getOrDefault ["taskId", ""];
|
||||||
|
private _order = +(_createData getOrDefault ["order", createHashMap]);
|
||||||
|
private _assignment = +(_createData getOrDefault ["assignment", createHashMap]);
|
||||||
|
if (_taskID isEqualTo "" || { _order isEqualTo createHashMap } || { _assignment isEqualTo createHashMap }) exitWith {
|
||||||
|
_result set ["message", "CAD extension returned an invalid dispatch order."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
||||||
|
_dispatchOrderRegistry set [_taskID, _order];
|
||||||
|
_self set ["dispatchOrderRegistry", _dispatchOrderRegistry];
|
||||||
|
|
||||||
|
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
||||||
_assignmentRegistry set [_taskID, _assignment];
|
_assignmentRegistry set [_taskID, _assignment];
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
_self set ["assignmentRegistry", _assignmentRegistry];
|
||||||
|
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityEntry = +(_createData getOrDefault ["activity", createHashMap]);
|
||||||
_activityRepository call ["appendActivity", [
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
"task_acknowledged",
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
format ["%1 acknowledged %2.", _requesterUid, _taskID],
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
||||||
_taskID,
|
};
|
||||||
_groupID,
|
|
||||||
_requesterUid
|
|
||||||
]];
|
|
||||||
|
|
||||||
_result set ["success", true];
|
_result set ["success", true];
|
||||||
_result set ["message", "Task acknowledged."];
|
_result set ["message", _createData getOrDefault ["message", "Dispatch order created."]];
|
||||||
_result set ["assignment", _assignment];
|
_result set ["assignment", _assignment];
|
||||||
|
_result set ["order", _order];
|
||||||
|
_result set ["leaderUid", _assigneeLeaderUid];
|
||||||
|
_result set ["isDispatchOrder", true];
|
||||||
_result
|
_result
|
||||||
}],
|
}],
|
||||||
["declineTask", compileFinal {
|
["closeDispatchOrder", compileFinal {
|
||||||
params [["_requesterUid", "", [""]], ["_taskID", "", [""]]];
|
params [["_requesterUid", "", [""]], ["_taskID", "", [""]]];
|
||||||
|
|
||||||
private _result = createHashMapFromArray [
|
private _result = createHashMapFromArray [
|
||||||
["success", false],
|
["success", false],
|
||||||
["message", "Unable to decline task."],
|
["message", "Unable to close dispatch order."],
|
||||||
["assignment", createHashMap]
|
["assignment", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
_self call ["restorePersistedState", []];
|
||||||
|
|
||||||
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
|
if !(_permissionService call ["canDispatch", [_requesterUid]]) exitWith {
|
||||||
|
_result set ["message", "You are not authorized to close dispatch orders."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _dispatchOrderRegistry = _self getOrDefault ["dispatchOrderRegistry", createHashMap];
|
||||||
|
private _order = +(_dispatchOrderRegistry getOrDefault [_taskID, createHashMap]);
|
||||||
|
if (_order isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Dispatch order could not be resolved."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
||||||
private _assignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
private _assignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
||||||
if (_assignment isEqualTo createHashMap) exitWith {
|
|
||||||
_result set ["message", "Task is not assigned."];
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
private _groupID = _assignment getOrDefault ["groupId", ""];
|
private _closeResult = _persistenceService call ["closeDispatchOrder", [_taskID]];
|
||||||
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
|
if !(_closeResult getOrDefault ["success", false]) exitWith {
|
||||||
if !(_groupRepository call ["isGroupLeader", [_requesterUid, _groupID]]) exitWith {
|
_result set ["message", "CAD extension rejected the dispatch order close."];
|
||||||
_result set ["message", "Only the assigned group leader can decline this task."];
|
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
_assignment set ["state", "declined"];
|
private _closeData = +(_closeResult getOrDefault ["data", createHashMap]);
|
||||||
_assignment set ["declinedAt", serverTime];
|
_order = +(_closeData getOrDefault ["order", _order]);
|
||||||
EGVAR(task,TaskStore) call ["releaseTaskOwnership", [_taskID]];
|
_assignment = +(_closeData getOrDefault ["assignment", _assignment]);
|
||||||
|
|
||||||
|
_dispatchOrderRegistry deleteAt _taskID;
|
||||||
|
_self set ["dispatchOrderRegistry", _dispatchOrderRegistry];
|
||||||
_assignmentRegistry deleteAt _taskID;
|
_assignmentRegistry deleteAt _taskID;
|
||||||
_self set ["assignmentRegistry", _assignmentRegistry];
|
_self set ["assignmentRegistry", _assignmentRegistry];
|
||||||
|
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityEntry = +(_closeData getOrDefault ["activity", createHashMap]);
|
||||||
_activityRepository call ["appendActivity", [
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
"task_declined",
|
_activityEntry set ["actorUid", _requesterUid];
|
||||||
format ["%1 declined %2.", _requesterUid, _taskID],
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
_taskID,
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
||||||
_groupID,
|
};
|
||||||
_requesterUid
|
|
||||||
]];
|
|
||||||
|
|
||||||
_result set ["success", true];
|
_result set ["success", true];
|
||||||
_result set ["message", "Task declined and returned to the contract board."];
|
_result set ["message", _closeData getOrDefault ["message", "Dispatch order closed."]];
|
||||||
_result set ["assignment", _assignment];
|
_result set ["assignment", _assignment];
|
||||||
|
_result set ["isDispatchOrder", true];
|
||||||
_result
|
_result
|
||||||
|
}],
|
||||||
|
["applyAssignmentTransition", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_taskID", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", "Unable to update task assignment."],
|
||||||
|
["assignment", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _transition = _this param [2, "acknowledge", [""]];
|
||||||
|
|
||||||
|
_self call ["restorePersistedState", []];
|
||||||
|
|
||||||
|
private _assignmentRegistry = _self getOrDefault ["assignmentRegistry", createHashMap];
|
||||||
|
private _assignment = +(_assignmentRegistry getOrDefault [_taskID, createHashMap]);
|
||||||
|
private _isDispatchOrder = _self call ["isDispatchOrder", [_taskID]];
|
||||||
|
if (_assignment isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Task is not assigned."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _groupID = _assignment getOrDefault ["groupId", ""];
|
||||||
|
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
|
||||||
|
if !(_groupRepository call ["isGroupLeader", [_requesterUid, _groupID]]) exitWith {
|
||||||
|
_result set ["message", format ["Only the assigned group leader can %1 this task.", _transition]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (_transition) do {
|
||||||
|
case "acknowledge": {
|
||||||
|
if (!_isDispatchOrder) then {
|
||||||
|
private _bindResult = EGVAR(task,TaskStore) call ["bindTaskOwnership", [_taskID, _requesterUid]];
|
||||||
|
if !(_bindResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", _bindResult getOrDefault ["message", "Failed to bind task ownership."]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case "decline": {
|
||||||
|
if (!_isDispatchOrder) then {
|
||||||
|
EGVAR(task,TaskStore) call ["releaseTaskOwnership", [_taskID]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_result getOrDefault ["success", false]) exitWith { _result };
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _patch = switch (_transition) do {
|
||||||
|
case "decline": {
|
||||||
|
createHashMapFromArray [
|
||||||
|
["state", "declined"],
|
||||||
|
["declinedAt", serverTime],
|
||||||
|
["declinedByUid", _requesterUid]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
createHashMapFromArray [
|
||||||
|
["state", "acknowledged"],
|
||||||
|
["acknowledgedAt", serverTime],
|
||||||
|
["acknowledgedByUid", _requesterUid]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
private _transitionResult = switch (_transition) do {
|
||||||
|
case "decline": { _persistenceService call ["declineAssignment", [_taskID, _patch]] };
|
||||||
|
default { _persistenceService call ["acknowledgeAssignment", [_taskID, _patch]] };
|
||||||
|
};
|
||||||
|
if !(_transitionResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", switch (_transition) do {
|
||||||
|
case "decline": { "CAD extension rejected the decline." };
|
||||||
|
default { "CAD extension rejected the acknowledgement." };
|
||||||
|
}];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _transitionData = +(_transitionResult getOrDefault ["data", createHashMap]);
|
||||||
|
_assignment = +(_transitionData getOrDefault ["assignment", createHashMap]);
|
||||||
|
if (_assignment isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension returned an invalid assignment."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (_transition) do {
|
||||||
|
case "decline": {
|
||||||
|
_assignmentRegistry deleteAt _taskID;
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
_assignmentRegistry set [_taskID, _assignment];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
_self set ["assignmentRegistry", _assignmentRegistry];
|
||||||
|
|
||||||
|
private _activityEntry = +(_transitionData getOrDefault ["activity", createHashMap]);
|
||||||
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
|
if (_isDispatchOrder) then {
|
||||||
|
_activityEntry set ["type", format ["dispatch_order_%1", _transition]];
|
||||||
|
_activityEntry set ["message", format ["%1 %2d %3.", _requesterUid, _transition, _taskID]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", switch (_transition) do {
|
||||||
|
case "decline": { [_transitionData getOrDefault ["message", "Task declined and returned to the contract board."], "Dispatch order declined and returned to the dispatch board."] select _isDispatchOrder };
|
||||||
|
default { [_transitionData getOrDefault ["message", "Task acknowledged."], "Dispatch order acknowledged."] select _isDispatchOrder };
|
||||||
|
}];
|
||||||
|
_result set ["assignment", _assignment];
|
||||||
|
_result set ["isDispatchOrder", _isDispatchOrder];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["acknowledgeTask", compileFinal {
|
||||||
|
_self call ["applyAssignmentTransition", [_this # 0, _this # 1, "acknowledge"]]
|
||||||
|
}],
|
||||||
|
["declineTask", compileFinal {
|
||||||
|
_self call ["applyAssignmentTransition", [_this # 0, _this # 1, "decline"]]
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -28,55 +28,35 @@ GVAR(CadStoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
private _permissionService = call FUNC(initPermissionService);
|
private _permissionService = call FUNC(initPermissionService);
|
||||||
private _groupRepository = call FUNC(initGroupRepository);
|
private _groupRepository = call FUNC(initGroupRepository);
|
||||||
private _assignmentRepository = call FUNC(initAssignmentRepository);
|
private _assignmentRepository = call FUNC(initAssignmentRepository);
|
||||||
|
private _persistenceService = call FUNC(initPersistenceService);
|
||||||
|
private _requestRepository = call FUNC(initRequestRepository);
|
||||||
|
|
||||||
_groupRepository set ["activityRepository", _activityRepository];
|
_groupRepository set ["activityRepository", _activityRepository];
|
||||||
_groupRepository set ["assignmentRepository", _assignmentRepository];
|
_groupRepository set ["assignmentRepository", _assignmentRepository];
|
||||||
_groupRepository set ["permissionService", _permissionService];
|
_groupRepository set ["permissionService", _permissionService];
|
||||||
|
_groupRepository set ["persistenceService", _persistenceService];
|
||||||
|
|
||||||
_assignmentRepository set ["activityRepository", _activityRepository];
|
_assignmentRepository set ["activityRepository", _activityRepository];
|
||||||
_assignmentRepository set ["groupRepository", _groupRepository];
|
_assignmentRepository set ["groupRepository", _groupRepository];
|
||||||
_assignmentRepository set ["permissionService", _permissionService];
|
_assignmentRepository set ["permissionService", _permissionService];
|
||||||
|
_assignmentRepository set ["persistenceService", _persistenceService];
|
||||||
|
|
||||||
|
_requestRepository set ["activityRepository", _activityRepository];
|
||||||
|
_requestRepository set ["groupRepository", _groupRepository];
|
||||||
|
_requestRepository set ["permissionService", _permissionService];
|
||||||
|
_requestRepository set ["persistenceService", _persistenceService];
|
||||||
|
|
||||||
|
_activityRepository set ["persistenceService", _persistenceService];
|
||||||
|
|
||||||
_self set ["ActivityRepository", _activityRepository];
|
_self set ["ActivityRepository", _activityRepository];
|
||||||
_self set ["PermissionService", _permissionService];
|
_self set ["PermissionService", _permissionService];
|
||||||
_self set ["GroupRepository", _groupRepository];
|
_self set ["GroupRepository", _groupRepository];
|
||||||
_self set ["AssignmentRepository", _assignmentRepository];
|
_self set ["AssignmentRepository", _assignmentRepository];
|
||||||
|
_self set ["PersistenceService", _persistenceService];
|
||||||
|
_self set ["RequestRepository", _requestRepository];
|
||||||
|
|
||||||
["INFO", "CAD Store Initialized!"] call EFUNC(common,log);
|
["INFO", "CAD Store Initialized!"] call EFUNC(common,log);
|
||||||
}],
|
}],
|
||||||
["appendActivity", compileFinal {
|
|
||||||
(_self get "ActivityRepository") call ["appendActivity", _this]
|
|
||||||
}],
|
|
||||||
["resolveGroupId", compileFinal {
|
|
||||||
(_self get "GroupRepository") call ["resolveGroupId", _this]
|
|
||||||
}],
|
|
||||||
["canDispatch", compileFinal {
|
|
||||||
(_self get "PermissionService") call ["canDispatch", _this]
|
|
||||||
}],
|
|
||||||
["getCurrentTaskIdForGroup", compileFinal {
|
|
||||||
(_self get "GroupRepository") call ["getCurrentTaskIdForGroup", _this]
|
|
||||||
}],
|
|
||||||
["syncGroups", compileFinal {
|
|
||||||
(_self get "GroupRepository") call ["syncGroups", _this]
|
|
||||||
}],
|
|
||||||
["getGroupRecord", compileFinal {
|
|
||||||
(_self get "GroupRepository") call ["getGroupRecord", _this]
|
|
||||||
}],
|
|
||||||
["getPlayerGroupId", compileFinal {
|
|
||||||
(_self get "GroupRepository") call ["getPlayerGroupId", _this]
|
|
||||||
}],
|
|
||||||
["isGroupLeader", compileFinal {
|
|
||||||
(_self get "GroupRepository") call ["isGroupLeader", _this]
|
|
||||||
}],
|
|
||||||
["pruneAssignments", compileFinal {
|
|
||||||
(_self get "AssignmentRepository") call ["pruneAssignments", _this]
|
|
||||||
}],
|
|
||||||
["buildContracts", compileFinal {
|
|
||||||
(_self get "AssignmentRepository") call ["buildContracts", _this]
|
|
||||||
}],
|
|
||||||
["buildGroups", compileFinal {
|
|
||||||
(_self get "GroupRepository") call ["buildGroups", _this]
|
|
||||||
}],
|
|
||||||
["notifyPlayer", compileFinal {
|
["notifyPlayer", compileFinal {
|
||||||
params [
|
params [
|
||||||
["_uid", "", [""]],
|
["_uid", "", [""]],
|
||||||
@ -93,23 +73,115 @@ GVAR(CadStoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
[CRPC(notifications,recieveNotification), [_type, _title, _message], _player] call CFUNC(targetEvent);
|
[CRPC(notifications,recieveNotification), [_type, _title, _message], _player] call CFUNC(targetEvent);
|
||||||
true
|
true
|
||||||
}],
|
}],
|
||||||
["assignTaskToGroup", compileFinal {
|
["resolveRequestPlayer", compileFinal {
|
||||||
private _result = (_self get "AssignmentRepository") call ["assignTaskToGroup", _this];
|
params [
|
||||||
if !(_result getOrDefault ["success", false]) exitWith { _result };
|
["_uid", "", [""]],
|
||||||
|
["_warning", "Invalid CAD payload.", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
["WARNING", _warning] call EFUNC(common,log);
|
||||||
|
objNull
|
||||||
|
};
|
||||||
|
|
||||||
|
[_uid] call EFUNC(common,getPlayer)
|
||||||
|
}],
|
||||||
|
["sendRpcResult", compileFinal {
|
||||||
|
params [
|
||||||
|
["_player", objNull, [objNull]],
|
||||||
|
["_responseRpc", "", [""]],
|
||||||
|
["_result", createHashMap, [createHashMap]],
|
||||||
|
["_invalidateOnSuccess", false, [false]],
|
||||||
|
["_requireChanged", false, [false]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_player isEqualTo objNull || { _responseRpc isEqualTo "" }) exitWith {};
|
||||||
|
|
||||||
|
[_responseRpc, [_result], _player] call CFUNC(targetEvent);
|
||||||
|
|
||||||
|
if (
|
||||||
|
_invalidateOnSuccess
|
||||||
|
&& { _result getOrDefault ["success", false] }
|
||||||
|
&& { !_requireChanged || { _result getOrDefault ["changed", true] } }
|
||||||
|
) then {
|
||||||
|
[CRPC(cad,invalidateCadState), []] call CFUNC(globalEvent);
|
||||||
|
};
|
||||||
|
}],
|
||||||
|
["dispatchRpcMutation", compileFinal {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_warning", "Invalid CAD payload.", [""]],
|
||||||
|
["_responseRpc", "", [""]],
|
||||||
|
["_method", "", [""]],
|
||||||
|
["_arguments", [], [[]]],
|
||||||
|
["_invalidateOnSuccess", false, [false]],
|
||||||
|
["_requireChanged", false, [false]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _player = _self call ["resolveRequestPlayer", [_uid, _warning]];
|
||||||
|
if (_player isEqualTo objNull || { _method isEqualTo "" }) exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _result = _self call [_method, _arguments];
|
||||||
|
_self call ["sendRpcResult", [_player, _responseRpc, _result, _invalidateOnSuccess, _requireChanged]];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["notifyAssignmentLeader", compileFinal {
|
||||||
|
params [["_result", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith { false };
|
||||||
|
|
||||||
private _assignment = _result getOrDefault ["assignment", createHashMap];
|
|
||||||
private _taskID = _assignment getOrDefault ["taskId", ""];
|
|
||||||
private _leaderUid = _result getOrDefault ["leaderUid", ""];
|
private _leaderUid = _result getOrDefault ["leaderUid", ""];
|
||||||
|
if (_leaderUid isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
private _message = if (_result getOrDefault ["isDispatchOrder", false]) then {
|
||||||
|
private _order = _result getOrDefault ["order", createHashMap];
|
||||||
|
if (_order isEqualTo createHashMap) then {
|
||||||
|
private _assignment = _result getOrDefault ["assignment", createHashMap];
|
||||||
|
private _taskID = _assignment getOrDefault ["taskId", ""];
|
||||||
|
_order = (_self get "AssignmentRepository") call ["buildDispatchOrderEntry", [
|
||||||
|
_taskID,
|
||||||
|
((_self get "AssignmentRepository") getOrDefault ["dispatchOrderRegistry", createHashMap]) getOrDefault [_taskID, createHashMap],
|
||||||
|
(_self get "AssignmentRepository") getOrDefault ["assignmentRegistry", createHashMap],
|
||||||
|
_self get "GroupRepository"
|
||||||
|
]];
|
||||||
|
};
|
||||||
|
|
||||||
|
format ["Dispatch order assigned: %1. Open CAD to review and acknowledge.", _order getOrDefault ["title", "Dispatch Order"]]
|
||||||
|
} else {
|
||||||
|
private _assignment = _result getOrDefault ["assignment", createHashMap];
|
||||||
|
format ["Contract assigned: %1. Open CAD to review and acknowledge.", _assignment getOrDefault ["taskId", "Task"]]
|
||||||
|
};
|
||||||
|
|
||||||
_self call ["notifyPlayer", [
|
_self call ["notifyPlayer", [
|
||||||
_leaderUid,
|
_leaderUid,
|
||||||
"info",
|
"info",
|
||||||
"Tasks",
|
"Tasks",
|
||||||
format ["Contract assigned: %1. Open CAD to review and acknowledge.", _taskID]
|
_message
|
||||||
]];
|
]]
|
||||||
|
}],
|
||||||
|
["assignTaskToGroup", compileFinal {
|
||||||
|
private _result = (_self get "AssignmentRepository") call ["assignTaskToGroup", _this];
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith { _result };
|
||||||
|
|
||||||
|
_self call ["notifyAssignmentLeader", [_result]];
|
||||||
_result
|
_result
|
||||||
}],
|
}],
|
||||||
|
["createDispatchOrder", compileFinal {
|
||||||
|
private _result = (_self get "AssignmentRepository") call ["createDispatchOrder", _this];
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith { _result };
|
||||||
|
|
||||||
|
_self call ["notifyAssignmentLeader", [_result]];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["closeDispatchOrder", compileFinal {
|
||||||
|
(_self get "AssignmentRepository") call ["closeDispatchOrder", _this]
|
||||||
|
}],
|
||||||
|
["submitSupportRequest", compileFinal {
|
||||||
|
(_self get "RequestRepository") call ["submitRequest", _this]
|
||||||
|
}],
|
||||||
|
["closeSupportRequest", compileFinal {
|
||||||
|
(_self get "RequestRepository") call ["closeRequest", _this]
|
||||||
|
}],
|
||||||
["acknowledgeTask", compileFinal {
|
["acknowledgeTask", compileFinal {
|
||||||
(_self get "AssignmentRepository") call ["acknowledgeTask", _this]
|
(_self get "AssignmentRepository") call ["acknowledgeTask", _this]
|
||||||
}],
|
}],
|
||||||
@ -122,13 +194,14 @@ GVAR(CadStoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["updateGroupRole", compileFinal {
|
["updateGroupRole", compileFinal {
|
||||||
(_self get "GroupRepository") call ["updateGroupRole", _this]
|
(_self get "GroupRepository") call ["updateGroupRole", _this]
|
||||||
}],
|
}],
|
||||||
|
["updateGroupProfile", compileFinal {
|
||||||
|
(_self get "GroupRepository") call ["updateGroupProfile", _this]
|
||||||
|
}],
|
||||||
["buildHydratePayload", compileFinal {
|
["buildHydratePayload", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
private _activityRepository = _self get "ActivityRepository";
|
|
||||||
private _permissionService = _self get "PermissionService";
|
private _permissionService = _self get "PermissionService";
|
||||||
private _groupRepository = _self get "GroupRepository";
|
private _groupRepository = _self get "GroupRepository";
|
||||||
private _assignmentRepository = _self get "AssignmentRepository";
|
|
||||||
|
|
||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||||
if (_actor isEqualTo createHashMap && { _uid isNotEqualTo "" }) then {
|
if (_actor isEqualTo createHashMap && { _uid isNotEqualTo "" }) then {
|
||||||
@ -136,20 +209,40 @@ GVAR(CadStoreBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _groupID = _groupRepository call ["getPlayerGroupId", [_uid]];
|
private _groupID = _groupRepository call ["getPlayerGroupId", [_uid]];
|
||||||
|
private _session = createHashMapFromArray [
|
||||||
createHashMapFromArray [
|
["uid", _uid],
|
||||||
|
["orgId", _actor getOrDefault ["organization", "default"]],
|
||||||
|
["isDispatcher", _permissionService call ["canDispatch", [_uid]]],
|
||||||
|
["groupId", _groupID],
|
||||||
|
["isLeader", _groupRepository call ["isGroupLeader", [_uid, _groupID]]]
|
||||||
|
];
|
||||||
|
private _seed = createHashMapFromArray [
|
||||||
["groups", _groupRepository call ["buildGroups", []]],
|
["groups", _groupRepository call ["buildGroups", []]],
|
||||||
["contracts", _assignmentRepository call ["buildContracts", [_uid]]],
|
["activeTasks", EGVAR(task,TaskStore) call ["getActiveTaskCatalog", []]],
|
||||||
["assignments", _assignmentRepository call ["getAssignments", []]],
|
["session", _session]
|
||||||
["activity", _activityRepository call ["getActivity", []]],
|
];
|
||||||
["session", createHashMapFromArray [
|
private _emptyPayload = createHashMapFromArray [
|
||||||
["uid", _uid],
|
["groups", _seed get "groups"],
|
||||||
["orgId", _actor getOrDefault ["organization", "default"]],
|
["contracts", []],
|
||||||
["isDispatcher", _permissionService call ["canDispatch", [_uid]]],
|
["requests", []],
|
||||||
["groupId", _groupID],
|
["assignments", []],
|
||||||
["isLeader", _groupRepository call ["isGroupLeader", [_uid, _groupID]]]
|
["activity", []],
|
||||||
]]
|
["session", _session]
|
||||||
]
|
];
|
||||||
|
private _persistenceService = _self getOrDefault ["PersistenceService", createHashMap];
|
||||||
|
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
["WARNING", "CAD hydrate extension state is unavailable; returning seed-only payload."] call EFUNC(common,log);
|
||||||
|
_emptyPayload
|
||||||
|
};
|
||||||
|
|
||||||
|
private _hydrateResult = _persistenceService call ["buildHydratePayload", [_seed]];
|
||||||
|
if (_hydrateResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_hydrateResult getOrDefault ["data", createHashMap]
|
||||||
|
};
|
||||||
|
|
||||||
|
["WARNING", "CAD hydrate failed in the extension; returning seed-only payload."] call EFUNC(common,log);
|
||||||
|
_emptyPayload
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -64,22 +64,31 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _assignmentRepository = _self getOrDefault ["assignmentRepository", createHashMap];
|
private _assignmentRepository = _self getOrDefault ["assignmentRepository", createHashMap];
|
||||||
private _assignmentRegistry = _assignmentRepository getOrDefault ["assignmentRegistry", createHashMap];
|
private _assignmentRegistry = _assignmentRepository getOrDefault ["assignmentRegistry", createHashMap];
|
||||||
|
private _dispatchOrderRegistry = _assignmentRepository getOrDefault ["dispatchOrderRegistry", createHashMap];
|
||||||
private _taskID = "";
|
private _taskID = "";
|
||||||
|
|
||||||
{
|
{
|
||||||
if ((_y getOrDefault ["groupId", ""]) isNotEqualTo _groupID) then { continue; };
|
if ((_y getOrDefault ["groupId", ""]) isNotEqualTo _groupID) then { continue; };
|
||||||
if !((_y getOrDefault ["state", ""]) in ["assigned", "acknowledged"]) then { continue; };
|
if !((_y getOrDefault ["state", ""]) in ["assigned", "acknowledged"]) then { continue; };
|
||||||
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) isNotEqualTo "active") then { continue; };
|
private _dispatchOrder = +(_dispatchOrderRegistry getOrDefault [_x, createHashMap]);
|
||||||
|
if (_dispatchOrder isEqualTo createHashMap) then {
|
||||||
|
if ((EGVAR(task,TaskStore) call ["getTaskStatus", [_x]]) isNotEqualTo "active") then { continue; };
|
||||||
|
_taskID = _x;
|
||||||
|
} else {
|
||||||
|
_taskID = _dispatchOrder getOrDefault ["title", _x];
|
||||||
|
};
|
||||||
|
|
||||||
_taskID = _x;
|
|
||||||
} forEach _assignmentRegistry;
|
} forEach _assignmentRegistry;
|
||||||
|
|
||||||
_taskID
|
_taskID
|
||||||
}],
|
}],
|
||||||
["syncGroups", compileFinal {
|
["syncGroups", compileFinal {
|
||||||
private _previousRegistry = _self getOrDefault ["groupRegistry", createHashMap];
|
private _assignmentRepository = _self getOrDefault ["assignmentRepository", createHashMap];
|
||||||
private _profileRegistry = _self getOrDefault ["groupProfileRegistry", createHashMap];
|
if (_assignmentRepository isNotEqualTo createHashMap) then {
|
||||||
private _nextRegistry = createHashMap;
|
_assignmentRepository call ["restorePersistedState", []];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _liveGroups = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
private _group = _x;
|
private _group = _x;
|
||||||
@ -104,9 +113,6 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
private _orgID = _actor getOrDefault ["organization", "default"];
|
private _orgID = _actor getOrDefault ["organization", "default"];
|
||||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||||
|
|
||||||
private _existingRecord = +(_previousRegistry getOrDefault [_groupID, createHashMap]);
|
|
||||||
private _profile = +(_profileRegistry getOrDefault [_groupID, createHashMap]);
|
|
||||||
private _memberUids = [];
|
private _memberUids = [];
|
||||||
private _memberRoster = [];
|
private _memberRoster = [];
|
||||||
|
|
||||||
@ -126,7 +132,7 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
]);
|
]);
|
||||||
} forEach _members;
|
} forEach _members;
|
||||||
|
|
||||||
private _record = createHashMapFromArray [
|
_liveGroups pushBack (createHashMapFromArray [
|
||||||
["groupId", _groupID],
|
["groupId", _groupID],
|
||||||
["callsign", [groupId _group, _groupID] select ((groupId _group) isEqualTo "")],
|
["callsign", [groupId _group, _groupID] select ((groupId _group) isEqualTo "")],
|
||||||
["leaderUid", _leaderUid],
|
["leaderUid", _leaderUid],
|
||||||
@ -134,20 +140,39 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
["memberUids", _memberUids],
|
["memberUids", _memberUids],
|
||||||
["members", _memberRoster],
|
["members", _memberRoster],
|
||||||
["orgId", _orgID],
|
["orgId", _orgID],
|
||||||
["role", [_existingRecord getOrDefault ["role", "infantry"], _profile getOrDefault ["role", _existingRecord getOrDefault ["role", "infantry"]]] select (_profile isNotEqualTo createHashMap)],
|
["role", "infantry"],
|
||||||
["status", [_existingRecord getOrDefault ["status", "available"], _profile getOrDefault ["status", _existingRecord getOrDefault ["status", "available"]]] select (_profile isNotEqualTo createHashMap)],
|
["status", "available"],
|
||||||
["position", getPosATL _leader],
|
["position", getPosATL _leader],
|
||||||
["currentTaskId", _self call ["getCurrentTaskIdForGroup", [_groupID]]],
|
["currentTaskId", _self call ["getCurrentTaskIdForGroup", [_groupID]]],
|
||||||
["lastUpdate", serverTime]
|
["lastUpdate", serverTime]
|
||||||
];
|
]);
|
||||||
|
|
||||||
_nextRegistry set [_groupID, _record];
|
|
||||||
_profileRegistry set [_groupID, createHashMapFromArray [
|
|
||||||
["role", _record getOrDefault ["role", "infantry"]],
|
|
||||||
["status", _record getOrDefault ["status", "available"]]
|
|
||||||
]];
|
|
||||||
} forEach allGroups;
|
} forEach allGroups;
|
||||||
|
|
||||||
|
private _mergedGroups = _liveGroups;
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isNotEqualTo createHashMap) then {
|
||||||
|
private _buildResult = _persistenceService call ["buildGroups", [_liveGroups]];
|
||||||
|
if (_buildResult getOrDefault ["success", false]) then {
|
||||||
|
_mergedGroups = +(_buildResult getOrDefault ["data", _liveGroups]);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _nextRegistry = createHashMap;
|
||||||
|
private _profileRegistry = createHashMap;
|
||||||
|
{
|
||||||
|
if !(_x isEqualType createHashMap) then { continue; };
|
||||||
|
private _groupID = _x getOrDefault ["groupId", ""];
|
||||||
|
if (_groupID isEqualTo "") then { continue; };
|
||||||
|
|
||||||
|
private _groupRecord = +_x;
|
||||||
|
_nextRegistry set [_groupID, _groupRecord];
|
||||||
|
_profileRegistry set [_groupID, createHashMapFromArray [
|
||||||
|
["groupId", _groupID],
|
||||||
|
["role", _groupRecord getOrDefault ["role", "infantry"]],
|
||||||
|
["status", _groupRecord getOrDefault ["status", "available"]]
|
||||||
|
]];
|
||||||
|
} forEach _mergedGroups;
|
||||||
|
|
||||||
_self set ["groupProfileRegistry", _profileRegistry];
|
_self set ["groupProfileRegistry", _profileRegistry];
|
||||||
_self set ["groupRegistry", _nextRegistry];
|
_self set ["groupRegistry", _nextRegistry];
|
||||||
_nextRegistry
|
_nextRegistry
|
||||||
@ -188,21 +213,52 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_groups
|
_groups
|
||||||
}],
|
}],
|
||||||
["updateGroupStatus", compileFinal {
|
["applyGroupProfileUpdate", compileFinal {
|
||||||
params [["_requesterUid", "", [""]], ["_groupID", "", [""]], ["_status", "", [""]]];
|
params [
|
||||||
|
["_requesterUid", "", [""]],
|
||||||
|
["_groupID", "", [""]],
|
||||||
|
["_status", "", [""]],
|
||||||
|
["_role", "", [""]],
|
||||||
|
["_mode", "profile", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
private _result = createHashMapFromArray [
|
private _result = createHashMapFromArray [
|
||||||
["success", false],
|
["success", false],
|
||||||
["message", "Unable to update group status."],
|
["message", "Unable to update group profile."],
|
||||||
|
["changed", false],
|
||||||
["group", createHashMap]
|
["group", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _finalStatus = toLowerANSI _status;
|
private _finalStatus = toLowerANSI _status;
|
||||||
if !(_finalStatus in (_self getOrDefault ["validStatuses", []])) exitWith {
|
private _finalRole = toLowerANSI _role;
|
||||||
|
private _hasStatus = _finalStatus isNotEqualTo "";
|
||||||
|
private _hasRole = _finalRole isNotEqualTo "";
|
||||||
|
|
||||||
|
if (_mode isEqualTo "status" && !_hasStatus) exitWith {
|
||||||
_result set ["message", "Invalid group status."];
|
_result set ["message", "Invalid group status."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (_mode isEqualTo "role" && !_hasRole) exitWith {
|
||||||
|
_result set ["message", "Invalid group role."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_mode isEqualTo "profile" && !(_hasStatus || _hasRole)) exitWith {
|
||||||
|
_result set ["message", "No group changes were provided."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_hasStatus && !(_finalStatus in (_self getOrDefault ["validStatuses", []]))) exitWith {
|
||||||
|
_result set ["message", "Invalid group status."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_hasRole && !(_finalRole in (_self getOrDefault ["validRoles", []]))) exitWith {
|
||||||
|
_result set ["message", "Invalid group role."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
private _isAuthorized = (_self call ["isGroupLeader", [_requesterUid, _groupID]]) || { _permissionService call ["canDispatch", [_requesterUid]] };
|
private _isAuthorized = (_self call ["isGroupLeader", [_requesterUid, _groupID]]) || { _permissionService call ["canDispatch", [_requesterUid]] };
|
||||||
if !_isAuthorized exitWith {
|
if !_isAuthorized exitWith {
|
||||||
@ -217,86 +273,68 @@ GVAR(GroupRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
|
|
||||||
_groupRecord set ["status", _finalStatus];
|
private _didChangeStatus = _hasStatus && { (_groupRecord getOrDefault ["status", ""]) isNotEqualTo _finalStatus };
|
||||||
|
private _didChangeRole = _hasRole && { (_groupRecord getOrDefault ["role", ""]) isNotEqualTo _finalRole };
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _updateContext = createHashMapFromArray [
|
||||||
|
["groupId", _groupID],
|
||||||
|
["groupCallsign", _groupRecord getOrDefault ["callsign", _groupID]],
|
||||||
|
["requesterUid", _requesterUid],
|
||||||
|
["currentRole", _groupRecord getOrDefault ["role", "infantry"]],
|
||||||
|
["currentStatus", _groupRecord getOrDefault ["status", "available"]],
|
||||||
|
["role", [_finalRole, ""] select !_hasRole],
|
||||||
|
["status", [_finalStatus, ""] select !_hasStatus],
|
||||||
|
["mode", _mode]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _profileResult = _persistenceService call ["updateGroupProfileFromContext", [_updateContext]];
|
||||||
|
if !(_profileResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension rejected the group profile update."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _profileData = +(_profileResult getOrDefault ["data", createHashMap]);
|
||||||
|
private _profile = +(_profileData getOrDefault ["profile", createHashMap]);
|
||||||
|
if (_profile isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension returned an invalid group profile."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
_groupRecord set ["role", _profile getOrDefault ["role", _groupRecord getOrDefault ["role", "infantry"]]];
|
||||||
|
_groupRecord set ["status", _profile getOrDefault ["status", _groupRecord getOrDefault ["status", "available"]]];
|
||||||
_groupRecord set ["lastUpdate", serverTime];
|
_groupRecord set ["lastUpdate", serverTime];
|
||||||
_groupRegistry set [_groupID, _groupRecord];
|
|
||||||
_self set ["groupRegistry", _groupRegistry];
|
|
||||||
|
|
||||||
private _profileRegistry = _self getOrDefault ["groupProfileRegistry", createHashMap];
|
private _profileRegistry = _self getOrDefault ["groupProfileRegistry", createHashMap];
|
||||||
private _profile = +(_profileRegistry getOrDefault [_groupID, createHashMap]);
|
_groupRegistry set [_groupID, _groupRecord];
|
||||||
_profile set ["role", _groupRecord getOrDefault ["role", "infantry"]];
|
_self set ["groupRegistry", _groupRegistry];
|
||||||
_profile set ["status", _finalStatus];
|
|
||||||
_profileRegistry set [_groupID, _profile];
|
_profileRegistry set [_groupID, _profile];
|
||||||
_self set ["groupProfileRegistry", _profileRegistry];
|
_self set ["groupProfileRegistry", _profileRegistry];
|
||||||
|
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
private _activityEntry = +(_profileData getOrDefault ["activity", createHashMap]);
|
||||||
_activityRepository call ["appendActivity", [
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
"group_status",
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
format ["%1 updated %2 to %3.", _requesterUid, _groupRecord getOrDefault ["callsign", _groupID], _finalStatus],
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
||||||
"",
|
};
|
||||||
_groupID,
|
|
||||||
_requesterUid
|
|
||||||
]];
|
|
||||||
|
|
||||||
_result set ["success", true];
|
_result set ["success", true];
|
||||||
_result set ["message", "Group status updated."];
|
_result set ["message", _profileData getOrDefault ["message", "Group profile updated."]];
|
||||||
|
_result set ["changed", _profileData getOrDefault ["changed", (_didChangeStatus || _didChangeRole)]];
|
||||||
_result set ["group", _groupRecord];
|
_result set ["group", _groupRecord];
|
||||||
_result
|
_result
|
||||||
}],
|
}],
|
||||||
|
["updateGroupStatus", compileFinal {
|
||||||
|
_self call ["applyGroupProfileUpdate", [_this # 0, _this # 1, _this # 2, "", "status"]]
|
||||||
|
}],
|
||||||
["updateGroupRole", compileFinal {
|
["updateGroupRole", compileFinal {
|
||||||
params [["_requesterUid", "", [""]], ["_groupID", "", [""]], ["_role", "", [""]]];
|
_self call ["applyGroupProfileUpdate", [_this # 0, _this # 1, "", _this # 2, "role"]]
|
||||||
|
}],
|
||||||
private _result = createHashMapFromArray [
|
["updateGroupProfile", compileFinal {
|
||||||
["success", false],
|
_self call ["applyGroupProfileUpdate", [_this # 0, _this # 1, _this # 2, _this # 3, "profile"]]
|
||||||
["message", "Unable to update group role."],
|
|
||||||
["group", createHashMap]
|
|
||||||
];
|
|
||||||
|
|
||||||
private _finalRole = toLowerANSI _role;
|
|
||||||
if !(_finalRole in (_self getOrDefault ["validRoles", []])) exitWith {
|
|
||||||
_result set ["message", "Invalid group role."];
|
|
||||||
_result
|
|
||||||
};
|
|
||||||
|
|
||||||
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
|
||||||
private _isAuthorized = (_self call ["isGroupLeader", [_requesterUid, _groupID]]) || { _permissionService call ["canDispatch", [_requesterUid]] };
|
|
||||||
if !_isAuthorized exitWith {
|
|
||||||
_result set ["message", "You are not authorized to update that group role."];
|
|
||||||
_result
|
|
||||||
};
|
|
||||||
|
|
||||||
private _groupRegistry = _self call ["syncGroups", []];
|
|
||||||
private _groupRecord = +(_groupRegistry getOrDefault [_groupID, createHashMap]);
|
|
||||||
if (_groupRecord isEqualTo createHashMap) exitWith {
|
|
||||||
_result set ["message", "Group could not be resolved."];
|
|
||||||
_result
|
|
||||||
};
|
|
||||||
|
|
||||||
_groupRecord set ["role", _finalRole];
|
|
||||||
_groupRecord set ["lastUpdate", serverTime];
|
|
||||||
_groupRegistry set [_groupID, _groupRecord];
|
|
||||||
_self set ["groupRegistry", _groupRegistry];
|
|
||||||
|
|
||||||
private _profileRegistry = _self getOrDefault ["groupProfileRegistry", createHashMap];
|
|
||||||
private _profile = +(_profileRegistry getOrDefault [_groupID, createHashMap]);
|
|
||||||
_profile set ["role", _finalRole];
|
|
||||||
_profile set ["status", _groupRecord getOrDefault ["status", "available"]];
|
|
||||||
_profileRegistry set [_groupID, _profile];
|
|
||||||
_self set ["groupProfileRegistry", _profileRegistry];
|
|
||||||
|
|
||||||
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
|
||||||
_activityRepository call ["appendActivity", [
|
|
||||||
"group_role",
|
|
||||||
format ["%1 updated %2 role to %3.", _requesterUid, _groupRecord getOrDefault ["callsign", _groupID], _finalRole],
|
|
||||||
"",
|
|
||||||
_groupID,
|
|
||||||
_requesterUid
|
|
||||||
]];
|
|
||||||
|
|
||||||
_result set ["success", true];
|
|
||||||
_result set ["message", "Group role updated."];
|
|
||||||
_result set ["group", _groupRecord];
|
|
||||||
_result
|
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
209
arma/server/addons/cad/functions/fnc_initPersistenceService.sqf
Normal file
209
arma/server/addons/cad/functions/fnc_initPersistenceService.sqf
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initPersistenceService.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-31
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the CAD extension-state service that bridges live SQF
|
||||||
|
* state to the Rust extension for hot CAD storage and recent history.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* CAD persistence service object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_server_cad_fnc_initPersistenceService
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(PersistenceServiceBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "CadPersistenceServiceBaseClass"],
|
||||||
|
["makeResult", compileFinal {
|
||||||
|
params [
|
||||||
|
["_success", false, [false]],
|
||||||
|
["_data", nil, [createHashMap, []]]
|
||||||
|
];
|
||||||
|
|
||||||
|
createHashMapFromArray [
|
||||||
|
["success", _success],
|
||||||
|
["data", _data]
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
["loadObject", compileFinal {
|
||||||
|
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||||
|
|
||||||
|
private _result = _self call ["makeResult", [false, createHashMap]];
|
||||||
|
if (_function isEqualTo "") exitWith { _result };
|
||||||
|
|
||||||
|
[_function, _arguments] call EFUNC(extension,extCall) params ["_payload", "_isSuccess"];
|
||||||
|
if (!_isSuccess || { !(_payload isEqualType "") } || { (_payload find "Error:") == 0 }) exitWith {
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _data = fromJSON _payload;
|
||||||
|
if !(_data isEqualType createHashMap) exitWith { _result };
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["data", _data];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["loadCollection", compileFinal {
|
||||||
|
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||||
|
|
||||||
|
private _result = _self call ["makeResult", [false, []]];
|
||||||
|
if (_function isEqualTo "") exitWith { _result };
|
||||||
|
|
||||||
|
[_function, _arguments] call EFUNC(extension,extCall) params ["_payload", "_isSuccess"];
|
||||||
|
if (!_isSuccess || { !(_payload isEqualType "") } || { (_payload find "Error:") == 0 }) exitWith {
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _data = fromJSON _payload;
|
||||||
|
if !(_data isEqualType []) exitWith { _result };
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["data", _data];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["loadRegistry", compileFinal {
|
||||||
|
params [["_function", "", [""]], ["_idField", "", [""]]];
|
||||||
|
|
||||||
|
private _result = _self call ["makeResult", [false, createHashMap]];
|
||||||
|
if (_function isEqualTo "" || { _idField isEqualTo "" }) exitWith { _result };
|
||||||
|
|
||||||
|
private _collectionResult = _self call ["loadCollection", [_function, []]];
|
||||||
|
if !(_collectionResult getOrDefault ["success", false]) exitWith { _result };
|
||||||
|
|
||||||
|
private _registry = createHashMap;
|
||||||
|
{
|
||||||
|
if !(_x isEqualType createHashMap) then { continue; };
|
||||||
|
private _entryId = _x getOrDefault [_idField, ""];
|
||||||
|
if (_entryId isEqualTo "") then { continue; };
|
||||||
|
_registry set [_entryId, +_x];
|
||||||
|
} forEach (_collectionResult getOrDefault ["data", []]);
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["data", _registry];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["saveEntry", compileFinal {
|
||||||
|
params [
|
||||||
|
["_function", "", [""]],
|
||||||
|
["_entryID", "", [""]],
|
||||||
|
["_entry", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_function isEqualTo "" || { _entryID isEqualTo "" } || { _entry isEqualTo createHashMap }) exitWith { false };
|
||||||
|
|
||||||
|
[_function, [_entryID, toJSON _entry]] call EFUNC(extension,extCall) params ["_payload", "_isSuccess"];
|
||||||
|
_isSuccess && { !(_payload isEqualType "") || { (_payload find "Error:") != 0 } }
|
||||||
|
}],
|
||||||
|
["deleteEntry", compileFinal {
|
||||||
|
params [["_function", "", [""]], ["_entryID", "", [""]]];
|
||||||
|
|
||||||
|
if (_function isEqualTo "" || { _entryID isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
[_function, [_entryID]] call EFUNC(extension,extCall) params ["_payload", "_isSuccess"];
|
||||||
|
_isSuccess && { !(_payload isEqualType "") || { (_payload find "Error:") != 0 } }
|
||||||
|
}],
|
||||||
|
["appendActivity", compileFinal {
|
||||||
|
params [["_entry", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
if (_entry isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
|
["cad:activity:append", [toJSON _entry]] call EFUNC(extension,extCall) params ["_payload", "_isSuccess"];
|
||||||
|
_isSuccess && { !(_payload isEqualType "") || { (_payload find "Error:") != 0 } }
|
||||||
|
}],
|
||||||
|
["loadActivity", compileFinal {
|
||||||
|
_self call ["loadCollection", ["cad:activity:recent", [str 50]]]
|
||||||
|
}],
|
||||||
|
["buildHydratePayload", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:view:hydrate", [toJSON (_this # 0)]]]
|
||||||
|
}],
|
||||||
|
["loadAssignments", compileFinal {
|
||||||
|
_self call ["loadRegistry", ["cad:assignments:list", "taskId"]]
|
||||||
|
}],
|
||||||
|
["assignAssignment", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:assignments:assign", [_this # 0, toJSON (_this # 1)]]]
|
||||||
|
}],
|
||||||
|
["acknowledgeAssignment", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:assignments:acknowledge", [_this # 0, toJSON (_this # 1)]]]
|
||||||
|
}],
|
||||||
|
["declineAssignment", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:assignments:decline", [_this # 0, toJSON (_this # 1)]]]
|
||||||
|
}],
|
||||||
|
["saveAssignment", compileFinal {
|
||||||
|
_self call ["saveEntry", ["cad:assignments:upsert", _this # 0, _this # 1]]
|
||||||
|
}],
|
||||||
|
["deleteAssignment", compileFinal {
|
||||||
|
_self call ["deleteEntry", ["cad:assignments:delete", _this # 0]]
|
||||||
|
}],
|
||||||
|
["loadDispatchOrders", compileFinal {
|
||||||
|
_self call ["loadRegistry", ["cad:orders:list", "taskID"]]
|
||||||
|
}],
|
||||||
|
["createDispatchOrder", compileFinal {
|
||||||
|
params [
|
||||||
|
["_orderSeed", createHashMap, [createHashMap]],
|
||||||
|
["_assignmentSeed", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
_self call ["loadObject", ["cad:orders:create", [toJSON (createHashMapFromArray [
|
||||||
|
["order", _orderSeed],
|
||||||
|
["assignment", _assignmentSeed]
|
||||||
|
])]]]
|
||||||
|
}],
|
||||||
|
["createDispatchOrderFromContext", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:orders:create_from_context", [toJSON (_this # 0)]]]
|
||||||
|
}],
|
||||||
|
["closeDispatchOrder", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:orders:close", [_this # 0]]]
|
||||||
|
}],
|
||||||
|
["saveDispatchOrder", compileFinal {
|
||||||
|
_self call ["saveEntry", ["cad:orders:upsert", _this # 0, _this # 1]]
|
||||||
|
}],
|
||||||
|
["deleteDispatchOrder", compileFinal {
|
||||||
|
_self call ["deleteEntry", ["cad:orders:delete", _this # 0]]
|
||||||
|
}],
|
||||||
|
["loadRequests", compileFinal {
|
||||||
|
_self call ["loadRegistry", ["cad:requests:list", "requestId"]]
|
||||||
|
}],
|
||||||
|
["submitSupportRequest", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:requests:submit", [toJSON (_this # 0)]]]
|
||||||
|
}],
|
||||||
|
["submitSupportRequestFromContext", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:requests:submit_from_context", [toJSON (_this # 0)]]]
|
||||||
|
}],
|
||||||
|
["closeSupportRequest", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:requests:close", [_this # 0]]]
|
||||||
|
}],
|
||||||
|
["saveRequest", compileFinal {
|
||||||
|
_self call ["saveEntry", ["cad:requests:upsert", _this # 0, _this # 1]]
|
||||||
|
}],
|
||||||
|
["deleteRequest", compileFinal {
|
||||||
|
_self call ["deleteEntry", ["cad:requests:delete", _this # 0]]
|
||||||
|
}],
|
||||||
|
["loadGroupProfiles", compileFinal {
|
||||||
|
_self call ["loadRegistry", ["cad:profiles:list", "groupId"]]
|
||||||
|
}],
|
||||||
|
["buildGroups", compileFinal {
|
||||||
|
_self call ["loadCollection", ["cad:groups:build", [toJSON (createHashMapFromArray [
|
||||||
|
["liveGroups", _this # 0]
|
||||||
|
])]]]
|
||||||
|
}],
|
||||||
|
["updateGroupProfileFromContext", compileFinal {
|
||||||
|
_self call ["loadObject", ["cad:profiles:update_from_context", [toJSON (_this # 0)]]]
|
||||||
|
}],
|
||||||
|
["saveGroupProfile", compileFinal {
|
||||||
|
_self call ["saveEntry", ["cad:profiles:upsert", _this # 0, _this # 1]]
|
||||||
|
}],
|
||||||
|
["deleteGroupProfile", compileFinal {
|
||||||
|
_self call ["deleteEntry", ["cad:profiles:delete", _this # 0]]
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
createHashMapObject [GVAR(PersistenceServiceBaseClass)]
|
||||||
208
arma/server/addons/cad/functions/fnc_initRequestRepository.sqf
Normal file
208
arma/server/addons/cad/functions/fnc_initRequestRepository.sqf
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initRequestRepository.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-31
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the CAD request repository for structured support
|
||||||
|
* requests submitted by groups and triaged by dispatch.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* CAD request repository object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* call forge_server_cad_fnc_initRequestRepository
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(RequestRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "CadRequestRepositoryBaseClass"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["requestRegistry", createHashMap];
|
||||||
|
_self set ["persistenceLoaded", false];
|
||||||
|
_self set ["validTypes", [
|
||||||
|
"medevac_9line",
|
||||||
|
"ace_lace",
|
||||||
|
"fire_support",
|
||||||
|
"air_support",
|
||||||
|
"logreq"
|
||||||
|
]];
|
||||||
|
_self set ["validPriorities", [
|
||||||
|
"routine",
|
||||||
|
"priority",
|
||||||
|
"emergency"
|
||||||
|
]];
|
||||||
|
}],
|
||||||
|
["restorePersistedState", compileFinal {
|
||||||
|
if (_self getOrDefault ["persistenceLoaded", false]) exitWith { true };
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith { false };
|
||||||
|
|
||||||
|
private _result = _persistenceService call ["loadRequests", []];
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith { false };
|
||||||
|
|
||||||
|
private _requestRegistry = +(_result getOrDefault ["data", createHashMap]);
|
||||||
|
|
||||||
|
_self set ["requestRegistry", _requestRegistry];
|
||||||
|
_self set ["persistenceLoaded", true];
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["submitRequest", compileFinal {
|
||||||
|
params [
|
||||||
|
["_requesterUid", "", [""]],
|
||||||
|
["_type", "", [""]],
|
||||||
|
["_fields", createHashMap, [createHashMap]],
|
||||||
|
["_priority", "priority", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", "Unable to submit support request."],
|
||||||
|
["request", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
_self call ["restorePersistedState", []];
|
||||||
|
|
||||||
|
private _finalType = toLowerANSI _type;
|
||||||
|
if !(_finalType in (_self getOrDefault ["validTypes", []])) exitWith {
|
||||||
|
_result set ["message", "Invalid support request type."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
|
||||||
|
private _groupID = _groupRepository call ["getPlayerGroupId", [_requesterUid]];
|
||||||
|
if (_groupID isEqualTo "") exitWith {
|
||||||
|
_result set ["message", "You are not currently assigned to a group."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
if !(_groupRepository call ["isGroupLeader", [_requesterUid, _groupID]]) exitWith {
|
||||||
|
_result set ["message", "Only the current group leader can submit support requests."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _groupRecord = _groupRepository call ["getGroupRecord", [_groupID]];
|
||||||
|
if (_groupRecord isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Your group could not be resolved."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _validPriorities = _self getOrDefault ["validPriorities", []];
|
||||||
|
private _finalPriority = toLowerANSI _priority;
|
||||||
|
if !(_finalPriority in _validPriorities) then {
|
||||||
|
_finalPriority = "priority";
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requestContext = createHashMapFromArray [
|
||||||
|
["type", _finalType],
|
||||||
|
["fields", +_fields],
|
||||||
|
["groupId", _groupID],
|
||||||
|
["groupCallsign", _groupRecord getOrDefault ["callsign", _groupID]],
|
||||||
|
["submittedByUid", _requesterUid],
|
||||||
|
["submittedByName", _groupRecord getOrDefault ["leaderName", _requesterUid]],
|
||||||
|
["priority", _finalPriority],
|
||||||
|
["position", +(_groupRecord getOrDefault ["position", []])],
|
||||||
|
["createdAt", serverTime]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _submitResult = _persistenceService call ["submitSupportRequestFromContext", [_requestContext]];
|
||||||
|
if !(_submitResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension rejected the support request."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _submitData = +(_submitResult getOrDefault ["data", createHashMap]);
|
||||||
|
private _request = +(_submitData getOrDefault ["request", createHashMap]);
|
||||||
|
private _requestID = _request getOrDefault ["requestId", ""];
|
||||||
|
if (_requestID isEqualTo "") exitWith {
|
||||||
|
_result set ["message", "CAD extension returned an invalid support request."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requestRegistry = _self getOrDefault ["requestRegistry", createHashMap];
|
||||||
|
_requestRegistry set [_requestID, _request];
|
||||||
|
_self set ["requestRegistry", _requestRegistry];
|
||||||
|
|
||||||
|
private _activityEntry = +(_submitData getOrDefault ["activity", createHashMap]);
|
||||||
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _submitData getOrDefault ["message", "Support request submitted."]];
|
||||||
|
_result set ["request", _request];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["closeRequest", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_requestID", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", "Unable to close support request."],
|
||||||
|
["request", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
_self call ["restorePersistedState", []];
|
||||||
|
|
||||||
|
private _requestRegistry = _self getOrDefault ["requestRegistry", createHashMap];
|
||||||
|
private _request = +(_requestRegistry getOrDefault [_requestID, createHashMap]);
|
||||||
|
if (_request isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Support request could not be resolved."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _permissionService = _self getOrDefault ["permissionService", createHashMap];
|
||||||
|
private _groupRepository = _self getOrDefault ["groupRepository", createHashMap];
|
||||||
|
private _groupID = _request getOrDefault ["groupId", ""];
|
||||||
|
private _isAuthorized = (_permissionService call ["canDispatch", [_requesterUid]]) || { _groupRepository call ["isGroupLeader", [_requesterUid, _groupID]] };
|
||||||
|
if !_isAuthorized exitWith {
|
||||||
|
_result set ["message", "You are not authorized to close that support request."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _persistenceService = _self getOrDefault ["persistenceService", createHashMap];
|
||||||
|
if (_persistenceService isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "CAD extension state is unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _closeResult = _persistenceService call ["closeSupportRequest", [_requestID]];
|
||||||
|
if !(_closeResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", "CAD extension rejected the support request close."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _closeData = +(_closeResult getOrDefault ["data", createHashMap]);
|
||||||
|
_request = +(_closeData getOrDefault ["request", _request]);
|
||||||
|
_requestRegistry deleteAt _requestID;
|
||||||
|
_self set ["requestRegistry", _requestRegistry];
|
||||||
|
|
||||||
|
private _activityEntry = +(_closeData getOrDefault ["activity", createHashMap]);
|
||||||
|
if (_activityEntry isNotEqualTo createHashMap) then {
|
||||||
|
_activityEntry set ["actorUid", _requesterUid];
|
||||||
|
private _activityRepository = _self getOrDefault ["activityRepository", createHashMap];
|
||||||
|
_activityRepository call ["appendEntry", [_activityEntry]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _closeData getOrDefault ["message", "Support request closed."]];
|
||||||
|
_result set ["request", _request];
|
||||||
|
_result
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
createHashMapObject [GVAR(RequestRepositoryBaseClass)]
|
||||||
@ -5,31 +5,3 @@ PREP_RECOMPILE_START;
|
|||||||
PREP_RECOMPILE_END;
|
PREP_RECOMPILE_END;
|
||||||
|
|
||||||
call FUNC(initTaskStore);
|
call FUNC(initTaskStore);
|
||||||
|
|
||||||
[QGVAR(requestTaskCatalog), {
|
|
||||||
params [["_uid", "", [""]]];
|
|
||||||
|
|
||||||
if (_uid isEqualTo "") exitWith {
|
|
||||||
["WARNING", "Task catalog request received with empty UID."] call EFUNC(common,log);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
|
||||||
|
|
||||||
[CRPC(cad,responseTaskCatalog), [GVAR(TaskStore) call ["getActiveTaskCatalog", []]], _player] call CFUNC(targetEvent);
|
|
||||||
}] call CFUNC(addEventHandler);
|
|
||||||
|
|
||||||
[QGVAR(requestAcceptTask), {
|
|
||||||
params [["_uid", "", [""]], ["_taskID", "", [""]]];
|
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _taskID isEqualTo "" }) exitWith {
|
|
||||||
["WARNING", "Invalid task accept request payload."] call EFUNC(common,log);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _player = [_uid] call EFUNC(common,getPlayer);
|
|
||||||
if (_player isEqualTo objNull) exitWith {};
|
|
||||||
|
|
||||||
private _result = GVAR(TaskStore) call ["acceptTask", [_taskID, _uid]];
|
|
||||||
[CRPC(cad,responseTaskAccept), [_result], _player] call CFUNC(targetEvent);
|
|
||||||
[CRPC(cad,responseTaskCatalog), [GVAR(TaskStore) call ["getActiveTaskCatalog", []]], _player] call CFUNC(targetEvent);
|
|
||||||
}] call CFUNC(addEventHandler);
|
|
||||||
|
|||||||
183
arma/server/extension/src/cad.rs
Normal file
183
arma/server/extension/src/cad.rs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
//! CAD hot-state operations for the Arma 3 server extension.
|
||||||
|
//!
|
||||||
|
//! The extension owns the in-memory CAD state store, while the shared service
|
||||||
|
//! layer handles mutation rules and hydrate shaping. This keeps the extension
|
||||||
|
//! surface thin and aligned with the workspace architecture.
|
||||||
|
|
||||||
|
use arma_rs::Group;
|
||||||
|
use forge_repositories::InMemoryCadRepository;
|
||||||
|
use forge_services::CadStateService;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
static CAD_SERVICE: LazyLock<CadStateService<InMemoryCadRepository>> =
|
||||||
|
LazyLock::new(|| CadStateService::new(InMemoryCadRepository::new()));
|
||||||
|
|
||||||
|
pub fn group() -> Group {
|
||||||
|
Group::new()
|
||||||
|
.group(
|
||||||
|
"activity",
|
||||||
|
Group::new()
|
||||||
|
.command("append", append_activity)
|
||||||
|
.command("recent", recent_activity),
|
||||||
|
)
|
||||||
|
.group(
|
||||||
|
"assignments",
|
||||||
|
Group::new()
|
||||||
|
.command("list", list_assignments)
|
||||||
|
.command("assign", assign_assignment)
|
||||||
|
.command("acknowledge", acknowledge_assignment)
|
||||||
|
.command("decline", decline_assignment)
|
||||||
|
.command("upsert", upsert_assignment)
|
||||||
|
.command("delete", delete_assignment),
|
||||||
|
)
|
||||||
|
.group(
|
||||||
|
"orders",
|
||||||
|
Group::new()
|
||||||
|
.command("list", list_orders)
|
||||||
|
.command("create", create_order)
|
||||||
|
.command("create_from_context", create_order_from_context)
|
||||||
|
.command("close", close_order)
|
||||||
|
.command("upsert", upsert_order)
|
||||||
|
.command("delete", delete_order),
|
||||||
|
)
|
||||||
|
.group(
|
||||||
|
"requests",
|
||||||
|
Group::new()
|
||||||
|
.command("list", list_requests)
|
||||||
|
.command("submit", submit_request)
|
||||||
|
.command("submit_from_context", submit_request_from_context)
|
||||||
|
.command("close", close_request)
|
||||||
|
.command("upsert", upsert_request)
|
||||||
|
.command("delete", delete_request),
|
||||||
|
)
|
||||||
|
.group(
|
||||||
|
"profiles",
|
||||||
|
Group::new()
|
||||||
|
.command("list", list_profiles)
|
||||||
|
.command("update_from_context", update_profile_from_context)
|
||||||
|
.command("upsert", upsert_profile)
|
||||||
|
.command("delete", delete_profile),
|
||||||
|
)
|
||||||
|
.group("groups", Group::new().command("build", build_groups))
|
||||||
|
.group("view", Group::new().command("hydrate", hydrate_view))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_activity(json_data: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.append_activity(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recent_activity(limit: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.recent_activity(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_assignments() -> String {
|
||||||
|
serialize_json(CAD_SERVICE.list_assignments())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_assignment(entry_id: String, json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.assign_assignment(entry_id, json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acknowledge_assignment(entry_id: String, json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.acknowledge_assignment(entry_id, json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decline_assignment(entry_id: String, json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.decline_assignment(entry_id, json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upsert_assignment(entry_id: String, json_data: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.upsert_assignment(entry_id, json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_assignment(entry_id: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.delete_assignment(entry_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_orders() -> String {
|
||||||
|
serialize_json(CAD_SERVICE.list_orders())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_order(json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.create_order(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_order_from_context(json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.create_order_from_context(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_order(entry_id: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.close_order(entry_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upsert_order(entry_id: String, json_data: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.upsert_order(entry_id, json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_order(entry_id: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.delete_order(entry_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_requests() -> String {
|
||||||
|
serialize_json(CAD_SERVICE.list_requests())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_request(json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.submit_request(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn submit_request_from_context(json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.submit_request_from_context(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_request(entry_id: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.close_request(entry_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upsert_request(entry_id: String, json_data: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.upsert_request(entry_id, json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_request(entry_id: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.delete_request(entry_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_profiles() -> String {
|
||||||
|
serialize_json(CAD_SERVICE.list_profiles())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_profile_from_context(json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.update_profile_from_context(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upsert_profile(entry_id: String, json_data: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.upsert_profile(entry_id, json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_profile(entry_id: String) -> String {
|
||||||
|
serialize_ok(CAD_SERVICE.delete_profile(entry_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_groups(json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.build_groups(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hydrate_view(json_data: String) -> String {
|
||||||
|
serialize_json(CAD_SERVICE.build_hydrate_payload(json_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_ok(result: Result<(), String>) -> String {
|
||||||
|
match result {
|
||||||
|
Ok(()) => "OK".to_string(),
|
||||||
|
Err(error) => format!("Error: {error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_json<T: Serialize>(result: Result<T, String>) -> String {
|
||||||
|
match result {
|
||||||
|
Ok(value) => serde_json::to_string(&value)
|
||||||
|
.unwrap_or_else(|error| format!("Error: Failed to serialize CAD state: {error}")),
|
||||||
|
Err(error) => format!("Error: {error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ use tokio::sync::RwLock as TokioRwLock;
|
|||||||
pub mod actor;
|
pub mod actor;
|
||||||
pub mod adapters;
|
pub mod adapters;
|
||||||
pub mod bank;
|
pub mod bank;
|
||||||
|
pub mod cad;
|
||||||
pub mod garage;
|
pub mod garage;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod icom;
|
pub mod icom;
|
||||||
@ -63,6 +64,7 @@ fn init() -> Extension {
|
|||||||
.group("redis", redis::group())
|
.group("redis", redis::group())
|
||||||
.group("actor", actor::group())
|
.group("actor", actor::group())
|
||||||
.group("bank", bank::group())
|
.group("bank", bank::group())
|
||||||
|
.group("cad", cad::group())
|
||||||
.group("garage", garage::group())
|
.group("garage", garage::group())
|
||||||
.group("icom", icom::group())
|
.group("icom", icom::group())
|
||||||
.group("locker", locker::group())
|
.group("locker", locker::group())
|
||||||
|
|||||||
227
lib/models/src/cad.rs
Normal file
227
lib/models/src/cad.rs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
pub type CadJsonMap = Map<String, Value>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct CadRecord {
|
||||||
|
pub fields: CadJsonMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CadRecord {
|
||||||
|
pub fn into_value(self) -> Value {
|
||||||
|
Value::Object(self.fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_value(&self) -> Value {
|
||||||
|
Value::Object(self.fields.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.fields.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge(mut self, patch: CadRecord) -> Self {
|
||||||
|
for (key, value) in patch.fields {
|
||||||
|
self.fields.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadDispatchOrderCreateSeed {
|
||||||
|
#[serde(default)]
|
||||||
|
pub order: CadRecord,
|
||||||
|
#[serde(default)]
|
||||||
|
pub assignment: CadRecord,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadDispatchOrderContextSeed {
|
||||||
|
#[serde(default)]
|
||||||
|
pub assignee_group_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub assignee_group_callsign: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub target_group_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub target_group_callsign: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub target_position: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub created_by_uid: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub created_by_name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub note: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub priority: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub created_at: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadDispatchOrderMutationResult {
|
||||||
|
#[serde(default)]
|
||||||
|
pub task_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub order: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub assignment: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub message: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub activity: CadActivityEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadActivityEntry {
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub entry_type: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub message: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub task_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub group_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub actor_uid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadAssignmentMutationResult {
|
||||||
|
#[serde(default)]
|
||||||
|
pub assignment: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub message: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub activity: CadActivityEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadRequestMutationResult {
|
||||||
|
#[serde(default)]
|
||||||
|
pub request: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub message: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub activity: CadActivityEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadGroupProfileUpdateSeed {
|
||||||
|
#[serde(default)]
|
||||||
|
pub group_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub group_callsign: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub requester_uid: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub current_role: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub current_status: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub role: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub status: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub mode: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadGroupProfileMutationResult {
|
||||||
|
#[serde(default)]
|
||||||
|
pub profile: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub message: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub activity: CadActivityEntry,
|
||||||
|
#[serde(default)]
|
||||||
|
pub changed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadSupportRequestSubmitSeed {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub request_type: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub fields: CadRecord,
|
||||||
|
#[serde(default)]
|
||||||
|
pub group_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub group_callsign: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub submitted_by_uid: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub submitted_by_name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub priority: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub position: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub created_at: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadGroupBuildSeed {
|
||||||
|
#[serde(default)]
|
||||||
|
pub live_groups: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadSession {
|
||||||
|
#[serde(default)]
|
||||||
|
pub uid: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub org_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub is_dispatcher: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub group_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub is_leader: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadHydrateSeed {
|
||||||
|
#[serde(default)]
|
||||||
|
pub groups: Vec<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub active_tasks: Vec<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub session: CadSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CadHydratePayload {
|
||||||
|
#[serde(default)]
|
||||||
|
pub groups: Vec<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub contracts: Vec<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub requests: Vec<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub assignments: Vec<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub activity: Vec<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub session: CadSession,
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
pub mod actor;
|
pub mod actor;
|
||||||
pub mod bank;
|
pub mod bank;
|
||||||
|
pub mod cad;
|
||||||
pub mod garage;
|
pub mod garage;
|
||||||
pub mod locker;
|
pub mod locker;
|
||||||
pub mod org;
|
pub mod org;
|
||||||
@ -8,6 +9,12 @@ pub mod v_locker;
|
|||||||
|
|
||||||
pub use actor::Actor;
|
pub use actor::Actor;
|
||||||
pub use bank::Bank;
|
pub use bank::Bank;
|
||||||
|
pub use cad::{
|
||||||
|
CadActivityEntry, CadAssignmentMutationResult, CadDispatchOrderContextSeed,
|
||||||
|
CadDispatchOrderCreateSeed, CadDispatchOrderMutationResult, CadGroupBuildSeed,
|
||||||
|
CadGroupProfileMutationResult, CadGroupProfileUpdateSeed, CadHydratePayload, CadHydrateSeed,
|
||||||
|
CadJsonMap, CadRecord, CadRequestMutationResult, CadSession, CadSupportRequestSubmitSeed,
|
||||||
|
};
|
||||||
pub use garage::{Garage, HitPoints, Vehicle};
|
pub use garage::{Garage, HitPoints, Vehicle};
|
||||||
pub use locker::{Item, Locker};
|
pub use locker::{Item, Locker};
|
||||||
pub use org::{CreditLineSummary, MemberSummary, Org, OrgAssetEntry, OrgFleetEntry};
|
pub use org::{CreditLineSummary, MemberSummary, Org, OrgAssetEntry, OrgFleetEntry};
|
||||||
|
|||||||
236
lib/repositories/src/cad.rs
Normal file
236
lib/repositories/src/cad.rs
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
use forge_models::CadRecord;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
const CAD_ACTIVITY_LIMIT: usize = 200;
|
||||||
|
|
||||||
|
pub trait CadRepository: Send + Sync {
|
||||||
|
fn append_activity(&self, entry: Value) -> Result<(), String>;
|
||||||
|
fn recent_activity(&self, limit: usize) -> Result<Vec<Value>, String>;
|
||||||
|
fn snapshot_activity(&self) -> Result<Vec<Value>, String>;
|
||||||
|
|
||||||
|
fn list_assignments(&self) -> Result<HashMap<String, CadRecord>, String>;
|
||||||
|
fn get_assignment(&self, id: &str) -> Result<Option<CadRecord>, String>;
|
||||||
|
fn save_assignment(&self, id: String, entry: CadRecord) -> Result<(), String>;
|
||||||
|
fn delete_assignment(&self, id: &str) -> Result<(), String>;
|
||||||
|
|
||||||
|
fn list_orders(&self) -> Result<HashMap<String, CadRecord>, String>;
|
||||||
|
fn get_order(&self, id: &str) -> Result<Option<CadRecord>, String>;
|
||||||
|
fn save_order(&self, id: String, entry: CadRecord) -> Result<(), String>;
|
||||||
|
fn delete_order(&self, id: &str) -> Result<(), String>;
|
||||||
|
|
||||||
|
fn list_requests(&self) -> Result<HashMap<String, CadRecord>, String>;
|
||||||
|
fn get_request(&self, id: &str) -> Result<Option<CadRecord>, String>;
|
||||||
|
fn save_request(&self, id: String, entry: CadRecord) -> Result<(), String>;
|
||||||
|
fn delete_request(&self, id: &str) -> Result<(), String>;
|
||||||
|
|
||||||
|
fn list_profiles(&self) -> Result<HashMap<String, CadRecord>, String>;
|
||||||
|
fn get_profile(&self, id: &str) -> Result<Option<CadRecord>, String>;
|
||||||
|
fn save_profile(&self, id: String, entry: CadRecord) -> Result<(), String>;
|
||||||
|
fn delete_profile(&self, id: &str) -> Result<(), String>;
|
||||||
|
|
||||||
|
fn next_order_id(&self) -> Result<String, String>;
|
||||||
|
fn next_request_id(&self) -> Result<String, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct CadState {
|
||||||
|
activity: Vec<Value>,
|
||||||
|
assignments: HashMap<String, CadRecord>,
|
||||||
|
orders: HashMap<String, CadRecord>,
|
||||||
|
requests: HashMap<String, CadRecord>,
|
||||||
|
profiles: HashMap<String, CadRecord>,
|
||||||
|
order_sequence: u64,
|
||||||
|
request_sequence: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct InMemoryCadRepository {
|
||||||
|
state: Arc<RwLock<CadState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMemoryCadRepository {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CadRepository for InMemoryCadRepository {
|
||||||
|
fn append_activity(&self, entry: Value) -> Result<(), String> {
|
||||||
|
let mut state = self
|
||||||
|
.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD activity state lock poisoned.".to_string())?;
|
||||||
|
|
||||||
|
state.activity.push(entry);
|
||||||
|
if state.activity.len() > CAD_ACTIVITY_LIMIT {
|
||||||
|
let overflow = state.activity.len() - CAD_ACTIVITY_LIMIT;
|
||||||
|
state.activity.drain(0..overflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recent_activity(&self, limit: usize) -> Result<Vec<Value>, String> {
|
||||||
|
let state = self
|
||||||
|
.state
|
||||||
|
.read()
|
||||||
|
.map_err(|_| "CAD activity state lock poisoned.".to_string())?;
|
||||||
|
let start = state.activity.len().saturating_sub(limit);
|
||||||
|
Ok(state.activity[start..].to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot_activity(&self) -> Result<Vec<Value>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.activity.clone())
|
||||||
|
.map_err(|_| "CAD activity state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_assignments(&self) -> Result<HashMap<String, CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.assignments.clone())
|
||||||
|
.map_err(|_| "CAD assignments state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_assignment(&self, id: &str) -> Result<Option<CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.assignments.get(id).cloned())
|
||||||
|
.map_err(|_| "CAD assignments state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_assignment(&self, id: String, entry: CadRecord) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD assignments state lock poisoned.".to_string())?
|
||||||
|
.assignments
|
||||||
|
.insert(id, entry);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_assignment(&self, id: &str) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD assignments state lock poisoned.".to_string())?
|
||||||
|
.assignments
|
||||||
|
.remove(id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_orders(&self) -> Result<HashMap<String, CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.orders.clone())
|
||||||
|
.map_err(|_| "CAD orders state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_order(&self, id: &str) -> Result<Option<CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.orders.get(id).cloned())
|
||||||
|
.map_err(|_| "CAD orders state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_order(&self, id: String, entry: CadRecord) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD orders state lock poisoned.".to_string())?
|
||||||
|
.orders
|
||||||
|
.insert(id, entry);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_order(&self, id: &str) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD orders state lock poisoned.".to_string())?
|
||||||
|
.orders
|
||||||
|
.remove(id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_requests(&self) -> Result<HashMap<String, CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.requests.clone())
|
||||||
|
.map_err(|_| "CAD requests state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request(&self, id: &str) -> Result<Option<CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.requests.get(id).cloned())
|
||||||
|
.map_err(|_| "CAD requests state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_request(&self, id: String, entry: CadRecord) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD requests state lock poisoned.".to_string())?
|
||||||
|
.requests
|
||||||
|
.insert(id, entry);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_request(&self, id: &str) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD requests state lock poisoned.".to_string())?
|
||||||
|
.requests
|
||||||
|
.remove(id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_profiles(&self) -> Result<HashMap<String, CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.profiles.clone())
|
||||||
|
.map_err(|_| "CAD profiles state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_profile(&self, id: &str) -> Result<Option<CadRecord>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.profiles.get(id).cloned())
|
||||||
|
.map_err(|_| "CAD profiles state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_profile(&self, id: String, entry: CadRecord) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD profiles state lock poisoned.".to_string())?
|
||||||
|
.profiles
|
||||||
|
.insert(id, entry);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_profile(&self, id: &str) -> Result<(), String> {
|
||||||
|
self.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD profiles state lock poisoned.".to_string())?
|
||||||
|
.profiles
|
||||||
|
.remove(id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_order_id(&self) -> Result<String, String> {
|
||||||
|
let mut state = self
|
||||||
|
.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD order sequence lock poisoned.".to_string())?;
|
||||||
|
state.order_sequence += 1;
|
||||||
|
Ok(format!("cad-order:{}", state.order_sequence))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_request_id(&self) -> Result<String, String> {
|
||||||
|
let mut state = self
|
||||||
|
.state
|
||||||
|
.write()
|
||||||
|
.map_err(|_| "CAD request sequence lock poisoned.".to_string())?;
|
||||||
|
state.request_sequence += 1;
|
||||||
|
Ok(format!("cad-request:{}", state.request_sequence))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
pub mod actor;
|
pub mod actor;
|
||||||
pub mod bank;
|
pub mod bank;
|
||||||
|
pub mod cad;
|
||||||
pub mod garage;
|
pub mod garage;
|
||||||
pub mod locker;
|
pub mod locker;
|
||||||
pub mod org;
|
pub mod org;
|
||||||
@ -8,6 +9,7 @@ pub mod v_locker;
|
|||||||
|
|
||||||
pub use actor::{ActorRepository, RedisActorRepository};
|
pub use actor::{ActorRepository, RedisActorRepository};
|
||||||
pub use bank::{BankRepository, RedisBankRepository};
|
pub use bank::{BankRepository, RedisBankRepository};
|
||||||
|
pub use cad::{CadRepository, InMemoryCadRepository};
|
||||||
pub use garage::{GarageRepository, RedisGarageRepository};
|
pub use garage::{GarageRepository, RedisGarageRepository};
|
||||||
pub use locker::{LockerRepository, RedisLockerRepository};
|
pub use locker::{LockerRepository, RedisLockerRepository};
|
||||||
pub use org::{OrgRepository, RedisOrgRepository};
|
pub use org::{OrgRepository, RedisOrgRepository};
|
||||||
|
|||||||
1092
lib/services/src/cad.rs
Normal file
1092
lib/services/src/cad.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
|||||||
pub mod actor;
|
pub mod actor;
|
||||||
pub mod bank;
|
pub mod bank;
|
||||||
|
pub mod cad;
|
||||||
pub mod garage;
|
pub mod garage;
|
||||||
pub mod locker;
|
pub mod locker;
|
||||||
pub mod org;
|
pub mod org;
|
||||||
@ -8,6 +9,7 @@ pub mod v_locker;
|
|||||||
|
|
||||||
pub use actor::ActorService;
|
pub use actor::ActorService;
|
||||||
pub use bank::BankService;
|
pub use bank::BankService;
|
||||||
|
pub use cad::{CadStateService, CadViewService};
|
||||||
pub use garage::GarageService;
|
pub use garage::GarageService;
|
||||||
pub use locker::LockerService;
|
pub use locker::LockerService;
|
||||||
pub use org::OrgService;
|
pub use org::OrgService;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user