Commit dc203ba1 by Mac Stephens

update map to hide details button on non-active units, update both details…

update map to hide details button on non-active units, update both details tables' style,  connect onclick to generated details buttons on lists, cleanup fbname code, fix scrolling issue on lists
parent f98f56bd
...@@ -20,6 +20,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -20,6 +20,8 @@ object ApiDatabaseModule: TApiDatabaseModule
' cus.CODE_DESC AS UNIT_STATUS_DESC,' ' cus.CODE_DESC AS UNIT_STATUS_DESC,'
' uc.UPDATETIME AS UPDATE_TIME,' ' uc.UPDATETIME AS UPDATE_TIME,'
'' ''
' dua.UNITID AS DIS_UNITID,'
''
' p1.PF_LNAME AS OFFICER1_LNAME,' ' p1.PF_LNAME AS OFFICER1_LNAME,'
' p1.PF_FNAME AS OFFICER1_FNAME,' ' p1.PF_FNAME AS OFFICER1_FNAME,'
' p1.PF_EMPNUM AS OFFICER1_EMPNUM,' ' p1.PF_EMPNUM AS OFFICER1_EMPNUM,'
...@@ -39,6 +41,7 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -39,6 +41,7 @@ object ApiDatabaseModule: TApiDatabaseModule
'LEFT JOIN PERSONNEL p1 ON p1.PF_NAMEID = dua.OFFICER1ID' 'LEFT JOIN PERSONNEL p1 ON p1.PF_NAMEID = dua.OFFICER1ID'
'LEFT JOIN PERSONNEL p2 ON p2.PF_NAMEID = dua.OFFICER2ID' 'LEFT JOIN PERSONNEL p2 ON p2.PF_NAMEID = dua.OFFICER2ID'
'' ''
''
'') '')
ReadOnly = True ReadOnly = True
Left = 464 Left = 464
...@@ -110,6 +113,10 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -110,6 +113,10 @@ object ApiDatabaseModule: TApiDatabaseModule
ReadOnly = True ReadOnly = True
Size = 10 Size = 10
end end
object uqMapUnitsDIS_UNITID: TFloatField
FieldName = 'DIS_UNITID'
ReadOnly = True
end
end end
object uqUnitList: TUniQuery object uqUnitList: TUniQuery
Connection = ucENTCAD Connection = ucENTCAD
......
...@@ -169,6 +169,7 @@ type ...@@ -169,6 +169,7 @@ type
uqUnitLogsLOG_TIME: TDateTimeField; uqUnitLogsLOG_TIME: TDateTimeField;
uqUnitLogsCOMPLAINT_NUM: TStringField; uqUnitLogsCOMPLAINT_NUM: TStringField;
uqUnitLogsLOG_TEXT: TStringField; uqUnitLogsLOG_TEXT: TStringField;
uqMapUnitsDIS_UNITID: TFloatField;
procedure uqComplaintListCalcFields(DataSet: TDataSet); procedure uqComplaintListCalcFields(DataSet: TDataSet);
procedure uqMapComplaintsCalcFields(DataSet: TDataSet); procedure uqMapComplaintsCalcFields(DataSet: TDataSet);
private private
......
...@@ -201,6 +201,10 @@ var ...@@ -201,6 +201,10 @@ var
begin begin
Logger.Log(4, '---TApiService.GetUnitMap initiated'); Logger.Log(4, '---TApiService.GetUnitMap initiated');
// Note: GetUnitMap is AVL-anchored (shows all AVL units).
// Note: DIS_UNITID is null when the unit is not dispatch-active; client should disable/hide Details in that case.
// Note: To restrict map to dispatch-active only, change uqMapUnits join to DIS_UNIT_ACTIVE from LEFT JOIN to INNER JOIN.
Result := TJSONObject.Create; Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result); TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
...@@ -245,6 +249,8 @@ begin ...@@ -245,6 +249,8 @@ begin
item.AddPair('Officer2Fname', ApiDB.uqMapUnitsOFFICER2_FNAME.AsString); item.AddPair('Officer2Fname', ApiDB.uqMapUnitsOFFICER2_FNAME.AsString);
item.AddPair('Officer2Empnum', ApiDB.uqMapUnitsOFFICER2_EMPNUM.AsString); item.AddPair('Officer2Empnum', ApiDB.uqMapUnitsOFFICER2_EMPNUM.AsString);
item.AddPair('CanShowDetails', TJSONBool.Create(not ApiDB.uqMapUnitsDIS_UNITID.IsNull));
data.AddElement(item); data.AddElement(item);
end; end;
...@@ -269,6 +275,7 @@ end; ...@@ -269,6 +275,7 @@ end;
function TApiService.GetComplaintList: TJSONObject; function TApiService.GetComplaintList: TJSONObject;
var var
data: TJSONArray; data: TJSONArray;
...@@ -545,7 +552,7 @@ begin ...@@ -545,7 +552,7 @@ begin
if ApiDB.uqCFSMemosTIMESTAMP.IsNull then if ApiDB.uqCFSMemosTIMESTAMP.IsNull then
ts := '' ts := ''
else else
ts := FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqCFSMemosTIMESTAMP.AsDateTime); ts := FormatDateTime('yyyy-mm-dd' + ' ' + 'hh:nn:ss', ApiDB.uqCFSMemosTIMESTAMP.AsDateTime);
item.AddPair('Timestamp', ts); item.AddPair('Timestamp', ts);
item.AddPair('BadgeNumber', ApiDB.uqCFSMemosBADGE_NUMBER.AsString); item.AddPair('BadgeNumber', ApiDB.uqCFSMemosBADGE_NUMBER.AsString);
...@@ -605,7 +612,7 @@ begin ...@@ -605,7 +612,7 @@ begin
if FieldByName('DATEREPORTED').IsNull then if FieldByName('DATEREPORTED').IsNull then
rowObj.AddPair('DateReported', '') rowObj.AddPair('DateReported', '')
else else
rowObj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd hh:nn:ss', FieldByName('DATEREPORTED').AsDateTime)); rowObj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd', FieldByName('DATEREPORTED').AsDateTime));
rowObj.AddPair('DPriority', FieldByName('DPRIORITY').AsString); rowObj.AddPair('DPriority', FieldByName('DPRIORITY').AsString);
rowObj.AddPair('DCallType', FieldByName('DCALLTYPE').AsString); rowObj.AddPair('DCallType', FieldByName('DCALLTYPE').AsString);
...@@ -780,11 +787,9 @@ begin ...@@ -780,11 +787,9 @@ begin
ts := ''; ts := '';
if not ApiDB.uqUnitLogsLOG_TIME.IsNull then if not ApiDB.uqUnitLogsLOG_TIME.IsNull then
ts := FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqUnitLogsLOG_TIME.AsDateTime); ts := FormatDateTime('yyyy-mm-dd' + ' ' + 'HH:nn:ss', ApiDB.uqUnitLogsLOG_TIME.AsDateTime);
complaintText := Trim(ApiDB.uqUnitLogsCOMPLAINT_NUM.AsString); complaintText := Trim(ApiDB.uqUnitLogsCOMPLAINT_NUM.AsString);
if complaintText = '0' then
complaintText := 'N/A';
rowObj.AddPair('LogTime', ts); rowObj.AddPair('LogTime', ts);
rowObj.AddPair('Complaint', complaintText); rowObj.AddPair('Complaint', complaintText);
......
[Settings] [Settings]
LogFileNum=557 LogFileNum=579
webClientVersion=0.1.0 webClientVersion=0.1.0
...@@ -143,12 +143,12 @@ object FViewComplaintDetails: TFViewComplaintDetails ...@@ -143,12 +143,12 @@ object FViewComplaintDetails: TFViewComplaintDetails
WordWrap = True WordWrap = True
Columns = < Columns = <
item item
ElementClassName = 'text-nowrap' ElementClassName = 'd-none'
DataField = 'MemoType' DataField = 'MemoType'
Title = 'Type' Title = 'Type'
TitleElementClassName = 'd-none'
end end
item item
ElementClassName = 'text-nowrap'
DataField = 'Timestamp' DataField = 'Timestamp'
Title = 'Timestamp' Title = 'Timestamp'
end end
...@@ -253,17 +253,17 @@ object FViewComplaintDetails: TFViewComplaintDetails ...@@ -253,17 +253,17 @@ object FViewComplaintDetails: TFViewComplaintDetails
item item
ElementClassName = 'text-nowrap' ElementClassName = 'text-nowrap'
DataField = 'Apartment' DataField = 'Apartment'
Title = 'Apartment' Title = 'Apt'
end end
item item
ElementClassName = 'text-nowrap' ElementClassName = 'text-nowrap'
DataField = 'DateReported' DataField = 'DateReported'
Title = 'Date Reported' Title = 'Date'
end end
item item
ElementClassName = 'text-nowrap' ElementClassName = 'text-nowrap'
DataField = 'DPriority' DataField = 'DPriority'
Title = 'Priority' Title = 'Pri'
end end
item item
DataField = 'DCallType' DataField = 'DCallType'
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
type="button" type="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#cdetails_summary" data-bs-target="#cdetails_summary"
aria-expanded="false" aria-expanded="true"
aria-controls="cdetails_summary"> aria-controls="cdetails_summary">
<span class="fw-semibold text-dark" id="lbl_summary_title">Summary</span> <span class="fw-semibold text-dark" id="lbl_summary_title">Summary</span>
<i class="fa fa-chevron-down"></i> <i class="fa fa-chevron-down"></i>
...@@ -21,26 +21,38 @@ ...@@ -21,26 +21,38 @@
</div> </div>
<!-- Summary body (stays sticky because it's inside the sticky block) --> <!-- Summary body (stays sticky because it's inside the sticky block) -->
<div id="cdetails_summary" class="collapse"> <div id="cdetails_summary" class="collapse show">
<div class="card border-0 shadow-sm mb-2"> <div class="card border-0 shadow-sm mb-2">
<div class="card-body py-2"> <div class="card-body py-2">
<div class="row gy-1 gx-2">
<div class="col-5 col-sm-4 fw-semibold text-muted">Priority:</div>
<div class="col-7 col-sm-8" id="lbl_priority"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Status:</div>
<div class="col-7 col-sm-8" id="lbl_status"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Dispatch Code:</div> <table class="table table-sm mb-0 w-100">
<div class="col-7 col-sm-8" id="lbl_dispatch_code"></div> <tbody>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Priority:</th>
<td class="w-100" id="lbl_priority"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Status:</th>
<td class="w-100" id="lbl_status"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Dispatch Code:</th>
<td class="w-100" id="lbl_dispatch_code"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Dispatch District:</th>
<td class="w-100" id="lbl_dispatch_district"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Address:</th>
<td class="w-100" id="lbl_address"></td>
</tr>
</tbody>
</table>
<div class="col-5 col-sm-4 fw-semibold text-muted">Dispatch District:</div>
<div class="col-7 col-sm-8" id="lbl_dispatch_district"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Address:</div>
<div class="col-7 col-sm-8" id="lbl_address"></div>
</div>
</div>
</div> </div>
</div> </div>
......
<div class="d-flex flex-column h-100">
<div class="sticky-top">
<!-- Local navbar (Complaints) --> <!-- Header / controls (non-scrolling) -->
<nav class="navbar navbar-dark bg-primary py-2"> <div class="flex-shrink-0">
<div class="container-fluid"> <!-- Local navbar (Complaints) -->
<div class="row w-100 g-2 align-items-stretch"> <nav class="navbar navbar-dark bg-primary py-2">
<div class="col"> <div class="container-fluid">
<span id="complaints_title" class="navbar-brand mb-0 h5 text-white">Complaints</span> <div class="row w-100 g-2 align-items-stretch">
</div> <div class="col">
<div class="col"> <span id="complaints_title" class="navbar-brand mb-0 h5 text-white">Complaints</span>
<button id="complaints_btnrefresh" type="button" class="btn btn-primary w-100 h-100"> </div>
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span> <div class="col">
</button> <button id="complaints_btnrefresh" type="button" class="btn btn-primary w-100 h-100">
</div> <i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
<div class="col"> </button>
<button id="complaints_btngroup" type="button" class="btn btn-primary w-100 h-100"> </div>
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span> <div class="col">
</button> <button id="complaints_btngroup" type="button" class="btn btn-primary w-100 h-100">
</div> <i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
<div class="col"> </button>
<button id="complaints_btnfilter" type="button" class="btn btn-primary w-100 h-100"> </div>
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span> <div class="col">
</button> <button id="complaints_btnfilter" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span>
</button>
</div>
</div> </div>
</div> </div>
</div> </nav>
</nav>
<!-- Search bar under local navbar -->
<!-- Search bar under local navbar --> <div class="bg-light border-bottom py-2">
<div class="bg-light border-bottom py-2"> <div class="container-fluid">
<div class="container-fluid"> <div class="input-group">
<div class="input-group"> <span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span> <input id="complaints_search" class="form-control" placeholder="Search...">
<input id="complaints_search" class="form-control" placeholder="Search..."> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <!-- Scrolling list area -->
<div class="flex-grow-1 overflow-auto" style="min-height:0;">
<!-- Complaints list container --> <div class="container-fluid mt-2">
<div class="container-fluid mt-2"> <div class="row justify-content-center">
<div class="row justify-content-center"> <div class="col-12 col-md-10 col-lg-8">
<div class="col-12 col-md-10 col-lg-8"> <!-- This is where the DBListControl will inject cards -->
<!-- This is where the DBListControl will inject cards --> <div id="complaints_dbl_complaint_list" class="d-flex flex-column gap-2">
<div id="complaints_dbl_complaint_list" class="d-flex flex-column gap-2"> <!-- Cards will render here -->
<!-- Cards will render here --> </div>
<label id="complaints_lblentries" class="mt-2 d-block"></label>
</div>
</div> </div>
<label id="complaints_lblentries" class="mt-2 d-block"></label>
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center" id="pagination">
<!-- Pagination items rendered in Delphi -->
</ul>
</nav>
</div> </div>
</div> </div>
</div>
</div>
...@@ -62,6 +62,18 @@ begin ...@@ -62,6 +62,18 @@ begin
tmrRefresh.Enabled := False; tmrRefresh.Enabled := False;
GetComplaints; GetComplaints;
tmrRefresh.Enabled := True; tmrRefresh.Enabled := True;
asm
if (!window.showComplaintDetails) {
window.showComplaintDetails = function (id) {
console.log('JS bridge showComplaintDetails called, id=', id);
try {
pas['View.Main'].FViewMain.ShowComplaintDetails(id);
} catch (e) {
console.log('Error in TFViewMain.ShowComplaintDetails', e);
}
};
}
end;
end; end;
procedure TFViewComplaints.HandleListClick(e: TJSMouseEvent); procedure TFViewComplaints.HandleListClick(e: TJSMouseEvent);
...@@ -70,12 +82,12 @@ begin ...@@ -70,12 +82,12 @@ begin
el := TJSElement(e.target); el := TJSElement(e.target);
if (el is TJSHtmlElement) and TJSHtmlElement(el).classList.contains('complaint-details-btn') then if (el is TJSHtmlElement) and TJSHtmlElement(el).classList.contains('complaint-details-btn') then
begin begin
id := string(TJSHtmlElement(el).dataset['id']); // comes from (%ComplaintId%) id := string(TJSHtmlElement(el).dataset['id']); // from data-id="(%ComplaintId%)"
e.preventDefault; e.preventDefault;
e.stopPropagation; e.stopPropagation;
asm
if Assigned(FSelectProc) then if (window.showComplaintDetails) window.showComplaintDetails(id);
FSelectProc(id); end;
end; end;
end; end;
......
...@@ -128,13 +128,8 @@ object FViewMap: TFViewMap ...@@ -128,13 +128,8 @@ object FViewMap: TFViewMap
end end
object tmrRefresh: TWebTimer object tmrRefresh: TWebTimer
Interval = 30000 Interval = 30000
OnTimer = tmrRefreshTimer
Left = 358 Left = 358
Top = 696 Top = 696
end end
object tmrZoomToBounds: TWebTimer
Enabled = False
OnTimer = tmrZoomToBoundsTimer
Left = 358
Top = 750
end
end end
...@@ -23,18 +23,18 @@ type ...@@ -23,18 +23,18 @@ type
httpReqGeoJson: TWebHttpRequest; httpReqGeoJson: TWebHttpRequest;
xdwcMap: TXDataWebClient; xdwcMap: TXDataWebClient;
tmrRefresh: TWebTimer; tmrRefresh: TWebTimer;
tmrZoomToBounds: TWebTimer;
procedure lfMapMapInitialized(Sender: TObject); procedure lfMapMapInitialized(Sender: TObject);
[async] procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string); [async] procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
procedure lfMapCustomizeMarker(Sender: TObject; procedure lfMapCustomizeMarker(Sender: TObject;
var ACustomizeMarker: string); var ACustomizeMarker: string);
procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string); procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
procedure tmrZoomToBoundsTimer(Sender: TObject); procedure tmrRefreshTimer(Sender: TObject);
private private
FUnitsLoaded: Boolean; FUnitsLoaded: Boolean;
FComplaintsLoaded: Boolean; FComplaintsLoaded: Boolean;
FZoomPending: Boolean; FZoomPending: Boolean;
FLoadingPoints: Boolean;
[async] procedure LoadPointsAsync; [async] procedure LoadPointsAsync;
function CarIconForDistrict(const DistrictCode: string): string; function CarIconForDistrict(const DistrictCode: string): string;
public public
...@@ -131,12 +131,6 @@ begin ...@@ -131,12 +131,6 @@ begin
P.StrokeWidth := 2; P.StrokeWidth := 2;
end; end;
if lfMap.Polygons.Count > 0 then
begin
FZoomPending := True;
tmrZoomToBounds.Enabled := True;
end;
finally finally
lfMap.EndUpdate; lfMap.EndUpdate;
end; end;
...@@ -180,6 +174,9 @@ var ...@@ -180,6 +174,9 @@ var
officer2Lname, officer2Fname, officer2Empnum: string; officer2Lname, officer2Fname, officer2Empnum: string;
officer1Display, officer2Display: string; officer1Display, officer2Display: string;
updateTimeText: string; updateTimeText: string;
canShowDetails: Boolean;
canShowDetailsText: string;
detailsBtnHtml: string;
begin begin
ShowSpinner('spinner'); ShowSpinner('spinner');
FUnitsLoaded := False; FUnitsLoaded := False;
...@@ -217,6 +214,24 @@ begin ...@@ -217,6 +214,24 @@ begin
officer2Fname := string(item['Officer2Fname']); officer2Fname := string(item['Officer2Fname']);
officer2Empnum := string(item['Officer2Empnum']); officer2Empnum := string(item['Officer2Empnum']);
canShowDetailsText := '';
if item['CanShowDetails'] <> nil then
canShowDetailsText := string(item['CanShowDetails']);
canShowDetails := SameText(Trim(canShowDetailsText), 'true');
if canShowDetails then
begin
detailsBtnHtml :=
'<button type="button" class="btn btn-primary btn-sm px-2 py-1" ' +
'onclick="window.showUnitDetails(''' + unitId + ''')">' +
'Details' +
'</button>';
end
else
begin
detailsBtnHtml := '';
end;
officer1Display := ''; officer1Display := '';
if Trim(officer1Lname + officer1Fname + officer1Empnum) <> '' then if Trim(officer1Lname + officer1Fname + officer1Empnum) <> '' then
begin begin
...@@ -242,47 +257,43 @@ begin ...@@ -242,47 +257,43 @@ begin
m.Longitude := lng; m.Longitude := lng;
m.Title := m.Title :=
'<div class="d-flex flex-column gap-1 px-1 py-1" style="width:260px;">' + '<div class="d-flex flex-column gap-1 px-1 py-1" style="width:260px;">' +
'<div class="fw-semibold small">' + '<div class="fw-semibold small">' +
'<span class="fw-bold">Unit:</span> ' + uName + '<span class="fw-bold">Unit:</span> ' + uName +
'</div>' + '</div>' +
IfThen(dist <> '', IfThen(dist <> '',
'<div class="small"><span class="fw-bold">District:</span> ' + dist + '</div>', '<div class="small"><span class="fw-bold">District:</span> ' + dist + '</div>',
'' ''
) + ) +
IfThen(Trim(callType) <> '', IfThen(Trim(callType) <> '',
'<div class="small"><span class="fw-bold">Call Type:</span> ' + callType + '</div>', '<div class="small"><span class="fw-bold">Call Type:</span> ' + callType + '</div>',
'' ''
) + ) +
IfThen(Trim(priorityText) <> '', IfThen(Trim(priorityText) <> '',
'<div class="small"><span class="fw-bold">Priority:</span> ' + priorityText + '</div>', '<div class="small"><span class="fw-bold">Priority:</span> ' + priorityText + '</div>',
'' ''
) + ) +
IfThen(Trim(statusText) <> '', IfThen(Trim(statusText) <> '',
'<div class="small"><span class="fw-bold">Status:</span> ' + statusText + '</div>', '<div class="small"><span class="fw-bold">Status:</span> ' + statusText + '</div>',
'' ''
) + ) +
IfThen(Trim(officer1Display) <> '',
IfThen(Trim(officer1Display) <> '', '<div class="small"><span class="fw-bold">Officer 1:</span> ' + officer1Display + '</div>',
'<div class="small"><span class="fw-bold">Officer 1:</span> ' + officer1Display + '</div>', ''
'' ) +
) + IfThen(Trim(officer2Display) <> '',
IfThen(Trim(officer2Display) <> '', '<div class="small"><span class="fw-bold">Officer 2:</span> ' + officer2Display + '</div>',
'<div class="small"><span class="fw-bold">Officer 2:</span> ' + officer2Display + '</div>', ''
'' ) +
) + IfThen(Trim(updateTimeText) <> '',
IfThen(Trim(updateTimeText) <> '', '<div class="small mb-1"><span class="fw-bold">Updated:</span> ' + updateTimeText + '</div>',
'<div class="small mb-1"><span class="fw-bold">Updated:</span> ' + updateTimeText + '</div>', '<div class="small mb-1"></div>'
'<div class="small mb-1"></div>' ) +
) + IfThen(detailsBtnHtml <> '',
'<div class="d-flex justify-content-end mt-0">' + detailsBtnHtml + '</div>',
'<div class="d-flex justify-content-end mt-0">' + ''
'<button type="button" class="btn btn-primary btn-sm px-2 py-1" ' + ) +
'onclick="window.showUnitDetails(''' + unitId + ''')">' + '</div>';
'Details' +
'</button>' +
'</div>' +
'</div>';
m.DataString := 'unit'; m.DataString := 'unit';
m.IconURL := CarIconForDistrict(dist); m.IconURL := CarIconForDistrict(dist);
...@@ -513,20 +524,12 @@ begin ...@@ -513,20 +524,12 @@ begin
'.emi-marker-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--bs-danger);color:#fff;font:700 11px/16px system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;box-shadow:0 0 0 2px #fff;}'; '.emi-marker-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--bs-danger);color:#fff;font:700 11px/16px system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;box-shadow:0 0 0 2px #fff;}';
end; end;
procedure TFViewMap.tmrZoomToBoundsTimer(Sender: TObject); procedure TFViewMap.tmrRefreshTimer(Sender: TObject);
begin begin
tmrZoomToBounds.Enabled := False; if FLoadingPoints then
if not FZoomPending then
Exit;
FZoomPending := False;
if lfMap.Polygons.Count = 0 then
Exit; Exit;
// Note: Delaying avoids Leaflet moveend firing while the internal map is still null. LoadPointsAsync;
lfMap.ZoomToBounds(lfMap.Polygons.ToCoordinateArray);
end; end;
end. end.
......
...@@ -5,11 +5,12 @@ object FViewUnitDetails: TFViewUnitDetails ...@@ -5,11 +5,12 @@ object FViewUnitDetails: TFViewUnitDetails
ElementFont = efCSS ElementFont = efCSS
object tblLogs: TWebDBTableControl object tblLogs: TWebDBTableControl
Left = 166 Left = 166
Top = 114 Top = 116
Width = 300 Width = 300
Height = 200 Height = 200
ElementId = 'tbl_logs' ElementId = 'tbl_logs'
BorderColor = clSilver BorderColor = clSilver
ColHeader = False
ElementFont = efCSS ElementFont = efCSS
ElementHeaderClassName = 'table-light' ElementHeaderClassName = 'table-light'
ElementTableClassName = ElementTableClassName =
...@@ -36,9 +37,9 @@ object FViewUnitDetails: TFViewUnitDetails ...@@ -36,9 +37,9 @@ object FViewUnitDetails: TFViewUnitDetails
WordWrap = True WordWrap = True
Columns = < Columns = <
item item
ElementClassName = 'text-nowrap'
DataField = 'LogTime' DataField = 'LogTime'
Title = 'Log Time' Title = 'Log Time'
TitleElementClassName = 'w-25'
end end
item item
ElementClassName = 'text-nowrap' ElementClassName = 'text-nowrap'
...@@ -57,7 +58,7 @@ object FViewUnitDetails: TFViewUnitDetails ...@@ -57,7 +58,7 @@ object FViewUnitDetails: TFViewUnitDetails
Top = 394 Top = 394
end end
object xdwdsUnitLogs: TXDataWebDataSet object xdwdsUnitLogs: TXDataWebDataSet
Left = 274 Left = 278
Top = 394 Top = 394
object xdwdsUnitLogsLogTime: TStringField object xdwdsUnitLogsLogTime: TStringField
FieldName = 'LogTime' FieldName = 'LogTime'
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
type="button" type="button"
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#udetails_summary" data-bs-target="#udetails_summary"
aria-expanded="false" aria-expanded="true"
aria-controls="udetails_summary"> aria-controls="udetails_summary">
<span class="fw-semibold text-dark" id="lbl_summary_title">Summary</span> <span class="fw-semibold text-dark" id="lbl_summary_title">Summary</span>
<i class="fa fa-chevron-down"></i> <i class="fa fa-chevron-down"></i>
...@@ -21,37 +21,49 @@ ...@@ -21,37 +21,49 @@
</div> </div>
<!-- Summary body --> <!-- Summary body -->
<div id="udetails_summary" class="collapse"> <div id="udetails_summary" class="collapse show">
<div class="card border-0 shadow-sm mb-2"> <div class="card border-0 shadow-sm mb-2">
<div class="card-body py-2"> <div class="card-body py-2">
<!-- Optional: Close/Back row (wire in code) --> <table class="table table-sm mb-0 w-100">
<tbody>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Car:</th>
<td class="w-100" id="lbl_car"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Unit Name:</th>
<td class="w-100" id="lbl_unit_name"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">District:</th>
<td class="w-100" id="lbl_district"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Location:</th>
<td class="w-100" id="lbl_location"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Status:</th>
<td class="w-100" id="lbl_status"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Officer 1:</th>
<td class="w-100" id="lbl_officer1"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Officer 2:</th>
<td class="w-100" id="lbl_officer2"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Updated:</th>
<td class="w-100" id="lbl_updated"></td>
</tr>
</tbody>
</table>
<div class="row gy-1 gx-2">
<div class="col-5 col-sm-4 fw-semibold text-muted">Car:</div>
<div class="col-7 col-sm-8" id="lbl_car"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Unit Name:</div>
<div class="col-7 col-sm-8" id="lbl_unit_name"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">District:</div>
<div class="col-7 col-sm-8" id="lbl_district"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Location:</div>
<div class="col-7 col-sm-8" id="lbl_location"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Status:</div>
<div class="col-7 col-sm-8" id="lbl_status"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Officer 1:</div>
<div class="col-7 col-sm-8" id="lbl_officer1"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Officer 2:</div>
<div class="col-7 col-sm-8" id="lbl_officer2"></div>
<div class="col-5 col-sm-4 fw-semibold text-muted">Updated:</div>
<div class="col-7 col-sm-8" id="lbl_updated"></div>
</div>
</div> </div>
</div> </div>
......
...@@ -35,9 +35,9 @@ type ...@@ -35,9 +35,9 @@ type
xdwdsUnitLogs: TXDataWebDataSet; xdwdsUnitLogs: TXDataWebDataSet;
wdsUnitLogs: TWebDataSource; wdsUnitLogs: TWebDataSource;
tblLogs: TWebDBTableControl; tblLogs: TWebDBTableControl;
xdwdsUnitLogsLogTime: TStringField;
xdwdsUnitLogsComplaint: TStringField; xdwdsUnitLogsComplaint: TStringField;
xdwdsUnitLogsLog: TStringField; xdwdsUnitLogsLog: TStringField;
xdwdsUnitLogsLogTime: TStringField;
private private
FUnitId: string; FUnitId: string;
FLoading: Boolean; FLoading: Boolean;
...@@ -51,6 +51,7 @@ type ...@@ -51,6 +51,7 @@ type
[async] procedure LoadUnitAsync; [async] procedure LoadUnitAsync;
[async] procedure LoadLogsAsync; [async] procedure LoadLogsAsync;
public public
class function CreateForm(AElementID, UnitId: string): TWebForm; class function CreateForm(AElementID, UnitId: string): TWebForm;
end; end;
...@@ -156,39 +157,36 @@ begin ...@@ -156,39 +157,36 @@ begin
xdwdsUnitDetails.Close; xdwdsUnitDetails.Close;
xdwdsUnitDetails.SetJsonData(dataObj); xdwdsUnitDetails.SetJsonData(dataObj);
xdwdsUnitDetails.Open; xdwdsUnitDetails.Open;
console.log('Officer1Lname', xdwdsUnitDetails.FieldByName('Officer1Lname').AsString);
console.log('Officer1Fname', xdwdsUnitDetails.FieldByName('Officer1Fname').AsString);
console.log('Officer1Empnum', xdwdsUnitDetails.FieldByName('Officer1Empnum').AsString);
console.log('Officer2Lname', xdwdsUnitDetails.FieldByName('Officer2Lname').AsString);
summaryTitle := xdwdsUnitDetails.FieldByName('UnitName').AsString; summaryTitle := xdwdsUnitDetailsUnitName.AsString;
if Trim(summaryTitle) <> '' then if Trim(summaryTitle) <> '' then
SetTextById('lbl_summary_title', 'Summary for Unit ' + summaryTitle) SetTextById('lbl_summary_title', 'Summary for Unit ' + summaryTitle)
else else
SetTextById('lbl_summary_title', 'Summary for Unit ' + xdwdsUnitDetails.FieldByName('UnitId').AsString); SetTextById('lbl_summary_title', 'Summary for Unit ' + xdwdsUnitDetailsUnitId.AsString);
SetTextById('lbl_car', xdwdsUnitDetails.FieldByName('CarNumberDesc').AsString); SetTextById('lbl_car', xdwdsUnitDetailsCarNumberDesc.AsString);
SetTextById('lbl_unit_name', xdwdsUnitDetails.FieldByName('UnitName').AsString); SetTextById('lbl_unit_name', xdwdsUnitDetailsUnitName.AsString);
SetTextById('lbl_district', xdwdsUnitDetails.FieldByName('District').AsString); SetTextById('lbl_district', xdwdsUnitDetailsDistrict.AsString);
SetTextById('lbl_location', xdwdsUnitDetails.FieldByName('Location').AsString); SetTextById('lbl_location', xdwdsUnitDetailsLocation.AsString);
SetTextById('lbl_status', xdwdsUnitDetails.FieldByName('Status').AsString); SetTextById('lbl_status', xdwdsUnitDetailsStatus.AsString);
SetTextById('lbl_updated', xdwdsUnitDetails.FieldByName('UpdateTime').AsString); SetTextById('lbl_updated', xdwdsUnitDetailsUpdateTime.AsString);
officer1Text := FormatOfficer( officer1Text := FormatOfficer(
xdwdsUnitDetails.FieldByName('Officer1Lname').AsString, xdwdsUnitDetailsOfficer1Lname.AsString,
xdwdsUnitDetails.FieldByName('Officer1Fname').AsString, xdwdsUnitDetailsOfficer1Fname.AsString,
xdwdsUnitDetails.FieldByName('Officer1Empnum').AsString xdwdsUnitDetailsOfficer1Empnum.AsString
); );
officer2Text := FormatOfficer( officer2Text := FormatOfficer(
xdwdsUnitDetails.FieldByName('Officer2Lname').AsString, xdwdsUnitDetailsOfficer2Lname.AsString,
xdwdsUnitDetails.FieldByName('Officer2Fname').AsString, xdwdsUnitDetailsOfficer2Fname.AsString,
xdwdsUnitDetails.FieldByName('Officer2Empnum').AsString xdwdsUnitDetailsOfficer2Empnum.AsString
); );
SetTextById('lbl_officer1', officer1Text); SetTextById('lbl_officer1', officer1Text);
SetTextById('lbl_officer2', officer2Text); SetTextById('lbl_officer2', officer2Text);
await(LoadLogsAsync); await(LoadLogsAsync);
Utils.HideSpinner('spinner'); Utils.HideSpinner('spinner');
...@@ -221,6 +219,7 @@ begin ...@@ -221,6 +219,7 @@ begin
resp := await(xdwcUnitDetails.RawInvokeAsync('IApiService.GetUnitLogs', [FUnitId])); resp := await(xdwcUnitDetails.RawInvokeAsync('IApiService.GetUnitLogs', [FUnitId]));
rootObj := TJSObject(resp.Result); rootObj := TJSObject(resp.Result);
dataArr := TJSArray(rootObj['data']); dataArr := TJSArray(rootObj['data']);
console.log('LoadLogsAsync Units rootObj: ' + rootObj.tostring);
xdwdsUnitLogs.Close; xdwdsUnitLogs.Close;
xdwdsUnitLogs.SetJsonData(dataArr); xdwdsUnitLogs.SetJsonData(dataArr);
......
<div class="d-flex flex-column h-100">
<div class="sticky-top">
<!-- Local navbar (Units) --> <!-- Header / controls (non-scrolling) -->
<nav class="navbar navbar-dark bg-primary py-2"><!-- removed sticky-top --> <div class="flex-shrink-0">
<div class="container-fluid"> <!-- Local navbar (Units) -->
<div class="row w-100 g-2 align-items-stretch"> <nav class="navbar navbar-dark bg-primary py-2">
<div class="col"> <div class="container-fluid">
<span id="units_title" class="navbar-brand mb-0 h5 text-white">Units</span> <div class="row w-100 g-2 align-items-stretch">
</div> <div class="col">
<div class="col"> <span id="units_title" class="navbar-brand mb-0 h5 text-white">Units</span>
<button id="units_btnrefresh" type="button" class="btn btn-primary w-100 h-100"> </div>
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span> <div class="col">
</button> <button id="units_btnrefresh" type="button" class="btn btn-primary w-100 h-100">
</div> <i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
<div class="col"> </button>
<button id="units_btngroup" type="button" class="btn btn-primary w-100 h-100"> </div>
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span> <div class="col">
</button> <button id="units_btngroup" type="button" class="btn btn-primary w-100 h-100">
</div> <i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
<div class="col"> </button>
<button id="units_btnfilter" type="button" class="btn btn-primary w-100 h-100"> </div>
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span> <div class="col">
</button> <button id="units_btnfilter" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span>
</button>
</div>
</div> </div>
</div> </div>
</div> </nav>
</nav>
<!-- Search bar under local navbar -->
<!-- Search bar under local navbar --> <div class="bg-light border-bottom py-2">
<div class="bg-light border-bottom py-2"><!-- removed sticky-top --> <div class="container-fluid">
<div class="container-fluid"> <div class="input-group">
<div class="input-group"> <span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span> <input id="units_search" class="form-control" placeholder="Search...">
<input id="units_search" class="form-control" placeholder="Search..."> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <!-- /sticky-top wrapper --> <!-- Scrolling list area -->
<div class="flex-grow-1 overflow-auto" style="min-height:0;">
<!-- Units list container --> <div class="container-fluid mt-2">
<div class="container-fluid mt-2"> <div class="row justify-content-center">
<div class="row justify-content-center"> <div class="col-12 col-md-10 col-lg-8">
<div class="col-12 col-md-10 col-lg-8"> <!-- This is where the DBListControl will inject cards -->
<!-- This is where the DBListControl will inject cards --> <div id="units_dbl_unit_list" class="d-flex flex-column gap-2">
<div id="units_dbl_unit_list" class="d-flex flex-column gap-2"> <!-- Cards will render here -->
<!-- Cards will render here --> </div>
<!-- Entry Count Label -->
<label id="unitss_lblentries" class="mt-2 d-block"></label>
</div>
</div> </div>
<!-- Entry Count Label -->
<label id="unitss_lblentries" class="mt-2 d-block"></label>
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center" id="pagination">
<!-- Pagination items rendered in Delphi -->
</ul>
</nav>
</div> </div>
</div> </div>
</div>
</div>
...@@ -35,6 +35,7 @@ type ...@@ -35,6 +35,7 @@ type
private private
FLoading: Boolean; FLoading: Boolean;
[async] procedure GetUnits; [async] procedure GetUnits;
procedure HandleListClick(e: TJSMouseEvent);
public public
end; end;
...@@ -49,36 +50,53 @@ implementation ...@@ -49,36 +50,53 @@ implementation
procedure TFViewUnits.WebFormCreate(Sender: TObject); procedure TFViewUnits.WebFormCreate(Sender: TObject);
begin begin
console.log('Units.WebFormCreate: Starting setup...');
DMConnection.ApiConnection.Connected := True; DMConnection.ApiConnection.Connected := True;
console.log('API connection active:', DMConnection.ApiConnection.Connected); Document.addEventListener('click', @HandleListClick);
tmrRefresh.Enabled := False; tmrRefresh.Enabled := False;
GetUnits; GetUnits;
tmrRefresh.Enabled := True; tmrRefresh.Enabled := True;
// {$IFNDEF WIN32} asm
// asm if (!window.showUnitDetails) {
// var root = pas.TFViewUnits(Self).dblUnitsList.ElementHandle; window.showUnitDetails = function (id) {
// if (root && !root.__emiDelegated) { console.log('JS bridge showUnitDetails called, id=', id);
// root.__emiDelegated = true; try {
// pas['View.Main'].FViewMain.ShowUnitDetails(id);
// root.addEventListener('click', function (e) { } catch (e) {
// // Look for a click on, or inside, the details button console.log('Error in TFViewMain.ShowUnitDetails', e);
// var btn = e.target && e.target.closest('.btn-unit-details'); }
// if (!btn || !root.contains(btn)) return; };
// }
// e.preventDefault(); end;
// e.stopPropagation(); end;
//
// var unitId = btn.getAttribute('data-unitid') || '';
// pas.TFViewUnits(Self).OpenUnitDetails(unitId); procedure TFViewUnits.HandleListClick(e: TJSMouseEvent);
// }, { passive: true }); var
// } el: TJSElement;
// end; btn: TJSElement;
// {$ENDIF} unitId: string;
begin
btn := nil;
el := TJSElement(e.target);
asm
btn = (el && el.closest) ? el.closest('.btn-unit-details') : null;
end;
if (btn <> nil) and (btn is TJSHtmlElement) then
begin
unitId := string(TJSHtmlElement(btn).getAttribute('data-unitid'));
e.preventDefault;
e.stopPropagation;
asm
if (window.showUnitDetails) window.showUnitDetails(unitId);
end;
end;
end; end;
procedure TFViewUnits.btnRefreshClick(Sender: TObject); procedure TFViewUnits.btnRefreshClick(Sender: TObject);
begin begin
GetUnits; GetUnits;
......
...@@ -68,3 +68,5 @@ html, body { ...@@ -68,3 +68,5 @@ html, body {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
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