Commit 65d81905 by Michael Brachmann

bring things up to date with changes in master

parents b98dbc3d ab0baace
......@@ -25,3 +25,5 @@ emiMobileServer/Source/__recovery/
*.log
*.dll
webEMIMobile/config/__history/
......@@ -211,11 +211,13 @@ type
procedure UniAlerter1Event(Sender: TDAAlerter; const EventName,
Message: string);
private
{ Private declarations }
public
CADUpdate: Boolean;
function HandleUniqueFilenames(const category: string): string;
function BadgeCounts(const BaseQuery: TUniQuery): Integer;
function EnsureConnected: Boolean;
end;
var
......@@ -240,7 +242,18 @@ begin
ucENTCAD.Password := IniEntries.DatabasePassword;
ucENTCAD.LoginPrompt := False;
EnsureConnected;
end;
function TApiDatabaseModule.EnsureConnected: Boolean;
begin
Result := False;
try
if not ucENTCAD.Connected then
begin
Logger.Log(2, 'Connecting to PostgreSQL API database...');
ucENTCAD.Connect;
ucENTCAD.ExecSQL('set search_path to lems, avl, entcad, public');
......@@ -249,6 +262,15 @@ begin
Logger.Log(1, 'Starting PostgreSQL disupdate listener');
UniAlerter1.Start;
Logger.Log(1, 'PostgreSQL disupdate listener started');
CADUpdate := True;
end;
Result := True;
except
on E: Exception do
Logger.Log(1, 'PostgreSQL API database unavailable: ' + E.Message);
end;
end;
procedure TApiDatabaseModule.uqComplaintListCalcFields(DataSet: TDataSet);
......
......@@ -23,6 +23,8 @@ type
procedure RequireActiveDevice;
function OpenLemsConnection: TUniConnection;
public
constructor Create;
destructor Destroy; override;
function GetBadgeCounts: TJSONObject;
function GetComplaintList: TJSONObject;
function GetUnitList: TJSONObject;
......@@ -48,22 +50,36 @@ implementation
uses
uLibrary;
procedure TApiService.AfterConstruction;
constructor TApiService.Create;
begin
inherited;
Logger.Log(3, 'TApiService.Create');
inherited Create;
ApiDB := TApiDatabaseModule.Create(nil);
if not ApiDB.ucENTCAD.Connected then
begin
Logger.Log(1, 'Unable to connect to API database');
raise EXDataHttpException.Create(
500,
'Unable to connect to the database. Please contact EM Systems support.'
);
end;
Logger.Log(3, 'ApiDatabaseModule created');
RequireActiveDevice;
end;
procedure TApiService.BeforeDestruction;
destructor TApiService.Destroy;
begin
ApiDB.Free;
inherited;
Logger.Log(3, 'ApiDatabaseModule destroyed');
inherited Destroy;
end;
function TApiService.GetBadgeCounts: TJSONObject;
begin
Logger.Log(3, '---TApiService.GetBadgeCounts initiated');
......
......@@ -55,10 +55,7 @@ begin
ucLemsOCSO.Connect;
except
on E: Exception do
begin
Logger.Log(2, Format('Failed to connect to auth database: %s', [E.Message]));
raise;
end;
end;
end;
......
......@@ -34,6 +34,9 @@ type
function Login(const user, password, agency, credentialId,
challengeToken, authenticatorData, clientDataJSON,
signature: string): string;
constructor Create;
destructor Destroy; override;
function Login(const User, Password, Agency: string): string;
function GetAgencieslist(): TAgenciesList;
function GetAgencyConfiglist: TAgencyConfigList;
function BeginRegistration(const PhoneNumber: string): TJSONObject;
......@@ -61,18 +64,28 @@ uses
{ TAuthService }
procedure TAuthService.AfterConstruction;
constructor TAuthService.Create;
begin
inherited;
Logger.Log(3, 'TAuthService.Create');
inherited Create;
authDB := TAuthDatabase.Create(nil);
if not authDB.ucLemsOCSO.Connected then
begin
Logger.Log(1, 'Unable to connect to auth database');
raise EXDataHttpException.Create(500, 'Unable to connect to the database. Please contact EM Systems support.');
end;
Logger.Log(3, 'AuthDatabase created');
end;
procedure TAuthService.BeforeDestruction;
destructor TAuthService.Destroy;
begin
authDB.Free;
inherited;
Logger.Log(3, 'AuthDatabase destroyed');
inherited Destroy;
end;
// ---------------------------------------------------------------------------
......@@ -806,6 +819,54 @@ begin
end;
end;
function TAuthService.Login(const User, Password, Agency: string): string;
var
userState: Integer;
JWT: TJWT;
begin
Logger.Log(1, Format('AuthService.Login - User: "%s" Agency: "%s"', [User, Agency]));
userState := CheckUser(User, Password, Agency);
try
userState := CheckUser(User, Password, Agency);
except
on E: Exception do
begin
Logger.Log(2, 'AuthService.Login - CheckUser error: ' + E.ClassName + ': ' + E.Message);
raise EXDataHttpException.Create(500, 'Login failed');
end;
end;
if userState = 0 then
begin
Logger.Log(2, Format('AuthService.Login - invalid login for User: "%s" Agency: "%s"', [User, Agency]));
raise EXDataHttpUnauthorized.Create('Invalid user or password');
end;
if userState = 1 then
begin
Logger.Log(2, Format('AuthService.Login - inactive user: "%s" Agency: "%s"', [User, Agency]));
raise EXDataHttpUnauthorized.Create('User not active');
end;
JWT := TJWT.Create;
try
JWT.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
JWT.Claims.IssuedAt := Now;
JWT.Claims.Expiration := IncHour(Now, 24);
JWT.Claims.SetClaimOfType<string>('user_name', userName);
JWT.Claims.SetClaimOfType<string>('user_fullname', userFullName);
JWT.Claims.SetClaimOfType<string>('user_agency', userAgency);
JWT.Claims.SetClaimOfType<string>('user_badge', userBadge);
JWT.Claims.SetClaimOfType<string>('user_id', userId);
JWT.Claims.SetClaimOfType<string>('user_personnelid', userPersonnelId);
Result := TJOSE.SHA256CompactToken(ServerConfig.jwtTokenSecret, JWT);
finally
JWT.Free;
end;
end;
function TAuthService.CheckUser(const User, Password, Agency: string): Integer;
var
userStr: string;
......
......@@ -148,7 +148,7 @@ begin
Result := '';
Msg := TStringList.Create;
try
Msg.Add(Format('%s %s %s',
Msg.Add(Format('%s %s %s %s',
[
FMethod,
FUriPath + FUriQuery,
......
......@@ -2,8 +2,8 @@ object FMain: TFMain
Left = 0
Top = 0
Caption = 'emiMobileServer'
ClientHeight = 616
ClientWidth = 772
ClientHeight = 773
ClientWidth = 778
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
......@@ -12,15 +12,16 @@ object FMain: TFMain
Font.Style = []
OnClose = FormClose
DesignSize = (
772
616)
778
773)
TextHeight = 13
object pgcMain: TPageControl
Left = 8
Top = 39
Width = 756
Height = 575
ActivePage = tabConnectedClients
Left = 0
Top = 40
Width = 778
Height = 733
ActivePage = tabServerLog
Align = alBottom
Anchors = [akLeft, akTop, akRight, akBottom]
TabOrder = 3
object tabServerLog: TTabSheet
......@@ -28,10 +29,9 @@ object FMain: TFMain
object memoInfo: TMemo
Left = 0
Top = 0
Width = 748
Height = 547
Width = 770
Height = 705
Align = alClient
ReadOnly = True
ScrollBars = ssVertical
TabOrder = 0
WordWrap = False
......@@ -43,8 +43,8 @@ object FMain: TFMain
object grdConnectedClients: TDBGrid
Left = 0
Top = 0
Width = 748
Height = 487
Width = 770
Height = 645
Align = alClient
DataSource = dsConnectedClients
Options = [dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect, dgConfirmDelete, dgCancelOnExit, dgTitleClick, dgTitleHotTrack]
......@@ -75,8 +75,8 @@ object FMain: TFMain
end
object pnlConnectedClientsActions: TPanel
Left = 0
Top = 487
Width = 748
Top = 645
Width = 770
Height = 60
Align = alBottom
Caption = 'pnlConnectedClientsActions'
......@@ -120,7 +120,7 @@ object FMain: TFMain
OnClick = btnApiSwaggerUIClick
end
object btnExit: TButton
Left = 713
Left = 695
Top = 8
Width = 75
Height = 25
......
......@@ -71,6 +71,9 @@ end;
procedure TWsDataModel.TimerFire(Sender: TObject);
begin
if not FDb.ucENTCAD.Connected then
Exit;
if not FDb.CADUpdate then
Exit;
......@@ -80,12 +83,13 @@ end;
procedure TWsDataModel.BroadcastAll;
begin
Logger.Log(3, 'WsDataModel: broadcasting all');
Logger.Log(5, 'WsDataModel: BroadcastAll initiated');
try FBroadcast(BuildBadgeCountsJson); except on E: Exception do Logger.Log(2, 'WsDataModel BADGE_COUNTS error: ' + E.Message); end;
try FBroadcast(BuildUnitMapJson); except on E: Exception do Logger.Log(2, 'WsDataModel UNIT_MAP error: ' + E.Message); end;
try FBroadcast(BuildComplaintMapJson); except on E: Exception do Logger.Log(2, 'WsDataModel COMPLAINT_MAP error: ' + E.Message); end;
try FBroadcast(BuildUnitListJson); except on E: Exception do Logger.Log(2, 'WsDataModel UNIT_LIST error: ' + E.Message); end;
try FBroadcast(BuildComplaintListJson); except on E: Exception do Logger.Log(2, 'WsDataModel COMPLAINT_LIST error: ' + E.Message); end;
Logger.Log(5, 'WsDataModel: BroadcastAll end');
end;
function TWsDataModel.BuildBadgeCountsJson: string;
......
[Settings]
LogFileNum=746
webClientVersion=9.4.0
LogFileNum=153
webClientVersion=0.9.4.1
[Database]
Server=192.168.102.10
--Server=192.168.74.10
Port=5432
Server=192.168.91.136
--Server=192.168.102.10
--Server=192.168.56.129
--Port=5432
--Port=5433
Database=lems_wcso
Username=postgres
--Password=postgreSQL
Password=emsys01
Password=postgreSQL
--Password=emsys01
--Postgre!SQL
{
"url": "http://localhost:2001/emsys/emiMobile/",
"jwtTokenSecret": "super_secret0123super_secret4567",
"adminPassword": "whatisthisusedfor?",
"webAppFolder": "static",
"memoLogLevel": 5,
"fileLogLevel": 5
}
\ No newline at end of file
......@@ -108,8 +108,9 @@
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>4</VerInfo_Release>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.2.0;Comments=</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.1;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.2.0;Comments=</VerInfo_Keys>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<VerInfo_Build>1</VerInfo_Build>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
......
......@@ -11,7 +11,6 @@ type
TSuccessProc = reference to procedure;
TLogoutProc = reference to procedure(AMessage: string = '');
TUnauthorizedAccessProc = reference to procedure(AMessage: string);
TVersionCheckCallback = reference to procedure(Success: Boolean; ErrorMessage: string);
TListProc = reference to procedure;
TSelectProc = reference to procedure(AParam: string);
......
......@@ -4,7 +4,7 @@ interface
uses
System.SysUtils, System.Classes, WEBLib.Modules, XData.Web.Connection,
App.Types, App.Config, XData.Web.Client;
XData.Web.Client, App.Types, App.Config;
type
TDMConnection = class(TWebDataModule)
......@@ -18,12 +18,12 @@ type
private
FUnauthorizedAccessProc: TUnauthorizedAccessProc;
FWsUrl: string;
procedure VerifyVersion(SuccessProc: TSuccessProc);
public
property WsUrl: string read FWsUrl;
const clientVersion = '9.4.0';
const clientVersion = '0.9.4.1';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback);
end;
var
......@@ -57,7 +57,8 @@ procedure TDMConnection.ApiConnectionResponse(
Args: TXDataWebConnectionResponse);
begin
if Args.Response.StatusCode = 401 then
FUnauthorizedAccessProc(Format('%d: %s',[Args.Response.StatusCode, Args.Response.ContentAsText]));
FUnauthorizedAccessProc(Format('%d: %s',
[Args.Response.StatusCode, Args.Response.ContentAsText]));
end;
procedure TDMConnection.AuthConnectionError(Error: TXDataWebConnectionError);
......@@ -79,7 +80,11 @@ procedure TDMConnection.InitApp(SuccessProc: TSuccessProc;
if Config.WsUrl <> '' then
FWsUrl := Config.WsUrl;
AuthConnection.Open(SuccessProc);
AuthConnection.Open(
procedure
begin
VerifyVersion(SuccessProc);
end);
end;
begin
......@@ -87,8 +92,7 @@ begin
LoadConfig(@ConfigLoaded);
end;
procedure TDMConnection.SetClientConfig(Callback: TVersionCheckCallback);
procedure TDMConnection.VerifyVersion(SuccessProc: TSuccessProc);
begin
XDataWebClient1.Connection := AuthConnection;
......@@ -100,15 +104,19 @@ begin
begin
jsonResult := TJSObject(Response.Result);
if jsonResult.HasOwnProperty('error') then
if Assigned(jsonResult) and jsonResult.HasOwnProperty('error') then
error := string(jsonResult['error'])
else
error := '';
if error <> '' then
Callback(False, error)
TFViewErrorPage.Display(error)
else
Callback(True, '');
SuccessProc;
end,
procedure(Error: TXDataClientError)
begin
TFViewErrorPage.Display(Error.ErrorMessage);
end);
end;
......
......@@ -15,6 +15,7 @@ function FormatPhoneNumber(PhoneNumber: string): string;
procedure ApplyReportTitle(CurrentReportType: string);
procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
procedure ShowInformationModal(const Title, MessageText: string);
// function FormatDollarValue(ValueStr: string): string;
......@@ -220,6 +221,28 @@ begin
end;
procedure ShowInformationModal(const Title, MessageText: string);
begin
asm
var modal = document.getElementById('main_information_modal');
var title = document.getElementById('main_information_modal_title');
var body = document.getElementById('main_information_modal_body');
if (!modal) return;
if (title) title.innerText = Title;
if (body) body.innerText = MessageText;
if (modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
var bsModal = bootstrap.Modal.getOrCreateInstance(modal);
bsModal.show();
end;
end;
function CalculateAge(DateOfBirth: TDateTime): Integer;
var
Today, BirthDate: TJSDate;
......
<div class="container">
<br />
<div class="row justify-content-center mt-5">
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
<div class="card shadow-sm border-danger">
<div class="panel panel-red">
<div id="view.errorpage.title" class="panel-heading">
Error Page
<div id="view.errorpage.title"
class="card-header bg-danger text-white fw-semibold">
Error
</div>
<div id="view.errorpage.message" class="panel-body">
<div class="card-body">
<div id="view.errorpage.message"
class="text-danger">
Message
</div>
</div>
<div class="card-footer bg-light text-end">
<a href="."
class="btn btn-outline-danger btn-sm">
Reload web application
</a>
</div>
<div class="panel-footer">
<a href=".">Reload web application</a>
</div>
</div>
</div>
</div>
......@@ -52,8 +52,9 @@
Login
</button>
</div>
<div class="card-footer text-muted small">
Please use your lems username &amp; password to login.
<div class="card-footer text-muted small d-flex justify-content-between">
<span>Please use your lems username &amp; password to login.</span>
<span id="view.login.version" class="opacity-75"></span>
</div>
</div>
</div>
......
......@@ -4,7 +4,7 @@ interface
uses
System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.JSON,
Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.JSON, Web,
JS, XData.Web.Connection, WEBLib.ExtCtrls,
App.Types, ConnectionModule, XData.Web.Client;
......@@ -67,8 +67,14 @@ begin
end;
procedure TFViewLogin.WebFormCreate(Sender: TObject);
var
el: TJSElement;
begin
GetAgencyConfigList;
el := Document.getElementById('view.login.version');
if Assigned(el) then
TJSHtmlElement(el).innerText := 'v' + TDMConnection.clientVersion;
GetAgencyConfigList();
if FMessage <> '' then
ShowNotification(FMessage)
else
......
......@@ -16,6 +16,7 @@
<div class="d-flex align-items-center gap-2 ms-auto">
<span id="view.main.lblconnection" class="navbar-text text-light small"></span>
<button id="btn_devices" type="button" class="btn btn-outline-light btn-sm d-none">Devices</button>
<span id="view.main.version" class="navbar-text text-light small opacity-75"></span>
<button id="btn_logout" type="button" class="btn btn-outline-light btn-sm">Logout</button>
</div>
</div>
......@@ -79,6 +80,23 @@
</nav>
</div>
<!-- Information modal -->
<div class="modal fade" id="main_information_modal" tabindex="-1"
aria-labelledby="main_information_modal_title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm mx-auto px-3">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="main_information_modal_title">Information</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="main_information_modal_body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-primary w-100" data-bs-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
<!-- Spinner -->
<div id="spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
......
......@@ -124,9 +124,15 @@ const
procedure TFViewMain.WebFormCreate(Sender: TObject);
var
userName: string;
el: TJSElement;
begin
userName := JS.toString(AuthService.TokenPayload.Properties['user_name']);
lblUsername.Caption := ' ' + userName.ToLower + ' ';
el := Document.getElementById('view.main.version');
if Assigned(el) then
TJSHtmlElement(el).innerText := 'v' + TDMConnection.clientVersion;
FChildForm := nil;
FDetailsForm := nil;
FArchiveForm := nil;
......
......@@ -37,6 +37,9 @@ type
FPendingFocusCoord: TTMSFNCMapsCoordinateRec;
FPendingFocusZoom: Integer;
FDoFocusZoom: Boolean;
FPendingFocusMarkerData: string;
FPendingWsUnitMapData: TJSArray;
FPendingWsComplaintMapData: TJSArray;
FGeoJsonLoadStep: Integer;
[async] procedure LoadPointsAsync(showBusy: Boolean);
......@@ -307,6 +310,22 @@ begin
Console.Log('Complaints XData error: ' + E.ErrorResult.ErrorMessage);
end;
// A WebSocket snapshot received while the HTTP requests were in flight is
// newer than those responses, so use the latest pushed data for the map.
if Assigned(FPendingWsUnitMapData) then
begin
unitsData := FPendingWsUnitMapData;
FPendingWsUnitMapData := nil;
FUnitsLoaded := True;
end;
if Assigned(FPendingWsComplaintMapData) then
begin
complaintsData := FPendingWsComplaintMapData;
FPendingWsComplaintMapData := nil;
FComplaintsLoaded := True;
end;
// --- Place markers (BeginUpdate wraps both so the map redraws once) ------
lfMap.BeginUpdate;
try
......@@ -553,8 +572,13 @@ end;
procedure TFViewMap.ApplyWsUnitMapData(aData: TJSArray);
begin
// Skip if the map is still initialising or an HTTP load is in flight.
if (not Assigned(mapFilters)) or FLoadingPoints then
if FLoadingPoints then
begin
FPendingWsUnitMapData := aData;
Exit;
end;
if not Assigned(mapFilters) then
Exit;
lfMap.BeginUpdate;
......@@ -570,7 +594,13 @@ end;
procedure TFViewMap.ApplyWsComplaintMapData(aData: TJSArray);
begin
if (not Assigned(mapFilters)) or FLoadingPoints then
if FLoadingPoints then
begin
FPendingWsComplaintMapData := aData;
Exit;
end;
if not Assigned(mapFilters) then
Exit;
lfMap.BeginUpdate;
......@@ -641,15 +671,47 @@ begin
end;
procedure TFViewMap.tmrLocateTimer(Sender: TObject);
var
i: Integer;
markerFound: Boolean;
missingTarget: string;
begin
tmrLocate.Enabled := False;
if not FDoFocusZoom then
Exit;
if FPendingFocusMarkerData <> '' then
begin
markerFound := False;
for i := 0 to lfMap.Markers.Count - 1 do
if SameText(lfMap.Markers[i].DataString, FPendingFocusMarkerData) or
StartsText(FPendingFocusMarkerData + '|', lfMap.Markers[i].DataString) then
begin
markerFound := True;
Break;
end;
if not markerFound then
begin
missingTarget := FPendingFocusMarkerData;
FPendingFocusMarkerData := '';
FDoFocusZoom := False;
if StartsText('unit|', missingTarget) then
ShowInformationModal('No Longer Available',
'This unit is no longer available on the map.')
else if StartsText('complaint|', missingTarget) then
ShowInformationModal('No Longer Available',
'This complaint is no longer active on the map.');
Exit;
end;
end;
lfMap.SetCenterCoordinate(FPendingFocusCoord);
lfMap.SetZoomLevel(FPendingFocusZoom);
FPendingFocusMarkerData := '';
FDoFocusZoom := False;
end;
......@@ -666,6 +728,7 @@ begin
FPendingFocusCoord := coord;
FPendingFocusZoom := 17;
FDoFocusZoom := True;
FPendingFocusMarkerData := '';
tmrLocate.Interval := 250;
tmrLocate.Enabled := True;
......@@ -674,6 +737,10 @@ end;
procedure TFViewMap.FocusUnit(const unitId: string);
begin
tmrLocate.Enabled := False;
FDoFocusZoom := False;
FPendingFocusMarkerData := '';
FPendingComplaintId := '';
FPendingUnitId := Trim(unitId);
if mapFilters <> nil then
LoadPointsAsync(True);
......@@ -682,6 +749,10 @@ end;
procedure TFViewMap.FocusComplaint(const complaintId: string);
begin
tmrLocate.Enabled := False;
FDoFocusZoom := False;
FPendingFocusMarkerData := '';
FPendingUnitId := '';
FPendingComplaintId := Trim(complaintId);
if mapFilters <> nil then
LoadPointsAsync(True);
......@@ -713,6 +784,7 @@ begin
FPendingFocusCoord := coord;
FPendingFocusZoom := 17;
FDoFocusZoom := True;
FPendingFocusMarkerData := targetDs;
tmrLocate.Interval := 250;
tmrLocate.Enabled := True;
......@@ -722,8 +794,15 @@ begin
end;
end;
if found then
FPendingUnitId := '';
if not found then
begin
FPendingFocusMarkerData := '';
FDoFocusZoom := False;
ShowInformationModal('No Longer Available',
'This unit is no longer available on the map.');
end;
end;
procedure TFViewMap.ApplyPendingComplaintFocus;
......@@ -751,6 +830,7 @@ begin
FPendingFocusCoord := coord;
FPendingFocusZoom := 17;
FDoFocusZoom := True;
FPendingFocusMarkerData := targetDs;
tmrLocate.Interval := 250;
tmrLocate.Enabled := True;
......@@ -760,8 +840,15 @@ begin
end;
end;
if found then
FPendingComplaintId := '';
if not found then
begin
FPendingFocusMarkerData := '';
FDoFocusZoom := False;
ShowInformationModal('No Longer Available',
'This complaint is no longer active on the map.');
end;
end;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment