Commit 80ed6f36 by emsys

Merge branch 'master' into websockets

parents 376416dd 63541f7e
......@@ -9,17 +9,19 @@ emiMobileServer/doc/
emiMobileServer/Win32/
emiMobileServer/*.log
emiMobileServer/*.txt
emiMobileServer/bin/logs/
emiMobileServer/bin/static/
emiMobileServer/Source/__history
emiMobileServer/Source/__recovery/
emiMobileServer/logs/*
*.local
*.exe
*.identcache
*.res
*.tvsconfig
*.dxsettings
*.zip
*.log
*.dll
......@@ -590,17 +590,66 @@ object ApiDatabaseModule: TApiDatabaseModule
' a.STRPREFIX,'
' a.STRNAME,'
' a.STRSUFFIX,'
' a.CITY'
' a.CITY,'
' 0 as status'
'FROM COMPLAINT_ACTIVE ca'
'JOIN COMPLAINT_TIMES ct'
' ON ca.COMPLAINTID = ct.COMPLAINTID'
'LEFT JOIN CD_DISPATCHCODES cdc'
' ON ca.DISPATCHCODE = cdc.CODE'
'JOIN CD_DISTRICT cd'
'LEFT JOIN CD_DISTRICT cd'
' ON cd.AGENCYCODE = ca.DISPATCHDISTRICT'
'LEFT JOIN ADDRESS a'
' ON ca.ADDRESSID = a.ADDRESSID'
'WHERE ca.COMPLAINTID = :COMPLAINTID'
''
'union'
''
'SELECT'
' ca.COMPLAINTID,'
' ca.CFSID,'
' ca.COMPLAINT,'
' ca.PRIORITY,'
' ca.DISPATCHCODE,'
' cdc.CODE_DESC AS DISPATCH_CODE_DESC,'
' cd.CODE_DESC AS DISPATCHDISTRICT,'
' ca.ADDRESS,'
' ca.BUSINESS,'
' ct.DATEREPORTED,'
' ct.DATERECEIVED,'
' ct.DATEDISPATCHED,'
' ct.DATERESPONDED,'
' ct.DATEARRIVED,'
' ct.DATECLEARED,'
''
' -- For enabling/disabling buttons (disable when = '#39'-1'#39')'
' ca.HISTORY,'
' ca.CONTACTS,'
' ca.WARNINGS,'
''
' -- For Contacts + Warnings lookups'
' ca.ADDRESSID,'
' ca.AGENCY,'
''
' -- For History matching (address parts)'
' a.STRNUMBER,'
' a.STRHNUMBER,'
' a.STRPREFIX,'
' a.STRNAME,'
' a.STRSUFFIX,'
' a.CITY,'
' 1 as status'
'FROM COMPLAINT_ARCHIVE ca'
'JOIN COMPLAINT_TIMES ct'
' ON ca.COMPLAINTID = ct.COMPLAINTID'
'LEFT JOIN CD_DISPATCHCODES cdc'
' ON ca.DISPATCHCODE = cdc.CODE'
'LEFT JOIN CD_DISTRICT cd'
' ON cd.AGENCYCODE = ca.DISPATCHDISTRICT'
'LEFT JOIN ADDRESS a'
' ON ca.ADDRESSID = a.ADDRESSID'
'WHERE ca.COMPLAINTID = :COMPLAINTID;')
'WHERE ca.COMPLAINTID = :COMPLAINTID;'
'')
ReadOnly = True
Left = 80
Top = 302
......@@ -713,6 +762,10 @@ object ApiDatabaseModule: TApiDatabaseModule
FieldName = 'BUSINESS'
Size = 35
end
object uqComplaintDetailsSTATUS: TFloatField
FieldName = 'STATUS'
ReadOnly = True
end
end
object ucENTCAD: TUniConnection
ProviderName = 'Oracle'
......@@ -807,8 +860,10 @@ object ApiDatabaseModule: TApiDatabaseModule
Calculated = True
end
object uqMapComplaintspngName: TStringField
DisplayWidth = 100
FieldKind = fkCalculated
FieldName = 'pngName'
Size = 100
Calculated = True
end
object uqMapComplaintsBUSINESS: TStringField
......@@ -908,6 +963,7 @@ object ApiDatabaseModule: TApiDatabaseModule
' where ca.complaintid = :COMPLAINTID'
')'
'select'
' c.complaintid,'
' c.complaint,'
' c.apartment,'
' c.datereported,'
......@@ -929,14 +985,39 @@ object ApiDatabaseModule: TApiDatabaseModule
'and (ctx.city is null or c.city = ctx.city)'
'order by c.datereported desc')
ReadOnly = True
Left = 328
Left = 330
Top = 72
ParamData = <
item
DataType = ftUnknown
Name = 'COMPLAINTID'
Value = nil
Value = Null
end>
object uqComplaintHistoryCOMPLAINTID: TFloatField
FieldName = 'COMPLAINTID'
Required = True
end
object uqComplaintHistoryCOMPLAINT: TStringField
FieldName = 'COMPLAINT'
Size = 10
end
object uqComplaintHistoryAPARTMENT: TStringField
FieldName = 'APARTMENT'
Size = 6
end
object uqComplaintHistoryDATEREPORTED: TDateTimeField
FieldName = 'DATEREPORTED'
end
object uqComplaintHistoryDPRIORITY: TStringField
FieldName = 'DPRIORITY'
ReadOnly = True
Size = 120
end
object uqComplaintHistoryDCALLTYPE: TStringField
FieldName = 'DCALLTYPE'
ReadOnly = True
Size = 60
end
end
object uqComplaintContacts: TUniQuery
Connection = ucENTCAD
......
......@@ -172,6 +172,13 @@ type
uqMapUnitsDIS_UNITID: TFloatField;
uqMapComplaintsBUSINESS: TStringField;
uqComplaintDetailsBUSINESS: TStringField;
uqComplaintDetailsSTATUS: TFloatField;
uqComplaintHistoryCOMPLAINTID: TFloatField;
uqComplaintHistoryCOMPLAINT: TStringField;
uqComplaintHistoryAPARTMENT: TStringField;
uqComplaintHistoryDATEREPORTED: TDateTimeField;
uqComplaintHistoryDPRIORITY: TStringField;
uqComplaintHistoryDCALLTYPE: TStringField;
procedure uqComplaintListCalcFields(DataSet: TDataSet);
procedure uqMapComplaintsCalcFields(DataSet: TDataSet);
private
......
......@@ -22,6 +22,7 @@ type
[HttpGet] function GetComplaintMap: TJSONObject;
[HttpGet] function GetUnitMap: TJSONObject;
[HttpGet] function GetComplaintDetails(const ComplaintId: string): TJSONObject;
[HttpGet] function GetComplaintArchiveDetails(const ComplaintId: string): TJSONObject;
[HttpGet] function GetComplaintMemos(const CfsId: string): TJSONObject;
[HttpGet] function GetComplaintHistory(const ComplaintId: string): TJSONObject;
[HttpGet] function GetComplaintContacts(const ComplaintId: string): TJSONObject;
......
......@@ -24,6 +24,7 @@ type
function GetComplaintMap: TJSONObject;
function GetUnitMap: TJSONObject;
function GetComplaintDetails(const ComplaintId: string): TJSONObject;
function GetComplaintArchiveDetails(const ComplaintId: string): TJSONObject;
function GetComplaintHistory(const ComplaintId: string): TJSONObject;
function GetComplaintContacts(const ComplaintId: string): TJSONObject;
function GetComplaintWarnings(const ComplaintId: string): TJSONObject;
......@@ -443,7 +444,6 @@ begin
end;
function TApiService.GetComplaintDetails(const ComplaintId: string): TJSONObject;
var
obj: TJSONObject;
......@@ -525,6 +525,91 @@ begin
end;
function TApiService.GetComplaintArchiveDetails(const ComplaintId: string): TJSONObject;
var
obj: TJSONObject;
begin
Logger.Log(3,'---TApiService.GetComplaintArchiveDetails initiated: '+ComplaintId);
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
try
with ApiDB.uqComplaintDetails do
begin
ParamByName('COMPLAINTID').AsString := ComplaintId;
Open;
try
if Eof then raise EXDataHttpException.Create(404,'Complaint not found');
if not Locate('STATUS', 1, []) then
raise EXDataHttpException.Create(404,'Complaint archive not found');
obj := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(obj);
obj.AddPair('ComplaintId', ApiDB.uqComplaintDetailsCOMPLAINTID.AsString);
obj.AddPair('CFSId', ApiDB.uqComplaintDetailsCFSID.AsString);
obj.AddPair('Complaint', ApiDB.uqComplaintDetailsCOMPLAINT.AsString);
obj.AddPair('Priority', ApiDB.uqComplaintDetailsPRIORITY.AsString);
obj.AddPair('DispatchCode', ApiDB.uqComplaintDetailsDISPATCHCODE.AsString);
obj.AddPair('DispatchCodeDesc', ApiDB.uqComplaintDetailsDISPATCH_CODE_DESC.AsString);
obj.AddPair('DispatchDistrict', ApiDB.uqComplaintDetailsDISPATCHDISTRICT.AsString);
obj.AddPair('Address', ApiDB.uqComplaintDetailsADDRESS.AsString);
obj.AddPair('Business', ApiDB.uqComplaintDetailsBUSINESS.AsString);
obj.AddPair('History', ApiDB.uqComplaintDetailsHISTORY.AsString);
obj.AddPair('Contacts', ApiDB.uqComplaintDetailsCONTACTS.AsString);
obj.AddPair('Warnings', ApiDB.uqComplaintDetailsWARNINGS.AsString);
if ApiDB.uqComplaintDetailsDATEREPORTED.IsNull then
obj.AddPair('DateReported', '')
else
obj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEREPORTED.AsDateTime));
if ApiDB.uqComplaintDetailsDATERECEIVED.IsNull then
obj.AddPair('DateReceived', '')
else
obj.AddPair('DateReceived', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATERECEIVED.AsDateTime));
if ApiDB.uqComplaintDetailsDATEDISPATCHED.IsNull then
obj.AddPair('DateDispatched', '')
else
obj.AddPair('DateDispatched', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEDISPATCHED.AsDateTime));
if ApiDB.uqComplaintDetailsDATERESPONDED.IsNull then
obj.AddPair('DateResponded', '')
else
obj.AddPair('DateResponded', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATERESPONDED.AsDateTime));
if ApiDB.uqComplaintDetailsDATEARRIVED.IsNull then
obj.AddPair('DateArrived', '')
else
obj.AddPair('DateArrived', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEARRIVED.AsDateTime));
if ApiDB.uqComplaintDetailsDATECLEARED.IsNull then
obj.AddPair('DateCleared', '')
else
obj.AddPair('DateCleared', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATECLEARED.AsDateTime));
Result.AddPair('data', obj);
finally
Close;
end;
end;
except
on E: EXDataHttpException do
begin
Logger.Log(3,'---TApiService.GetComplaintArchiveDetails not found');
raise;
end;
on E: Exception do
begin
Logger.Log(3,'---TApiService.GetComplaintArchiveDetails error: '+E.Message);
raise EXDataHttpException.Create(500,'Failed to load complaint archive details');
end;
end;
Logger.Log(3,'---TApiService.GetComplaintArchiveDetails End');
end;
function TApiService.GetComplaintMemos(const CfsId: string): TJSONObject;
var
data: TJSONArray;
......@@ -612,16 +697,17 @@ begin
rowObj := TJSONObject.Create;
dataArr.AddElement(rowObj);
rowObj.AddPair('Complaint', FieldByName('COMPLAINT').AsString);
rowObj.AddPair('Apartment', FieldByName('APARTMENT').AsString);
rowObj.AddPair('ComplaintId', ApiDB.uqComplaintHistoryCOMPLAINTID.AsString);
rowObj.AddPair('Complaint', ApiDB.uqComplaintHistoryCOMPLAINT.AsString);
rowObj.AddPair('Apartment', ApiDB.uqComplaintHistoryAPARTMENT.AsString);
if FieldByName('DATEREPORTED').IsNull then
if ApiDB.uqComplaintHistoryDATEREPORTED.IsNull then
rowObj.AddPair('DateReported', '')
else
rowObj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd', FieldByName('DATEREPORTED').AsDateTime));
rowObj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd', ApiDB.uqComplaintHistoryDATEREPORTED.AsDateTime));
rowObj.AddPair('DPriority', FieldByName('DPRIORITY').AsString);
rowObj.AddPair('DCallType', FieldByName('DCALLTYPE').AsString);
rowObj.AddPair('DPriority', ApiDB.uqComplaintHistoryDPRIORITY.AsString);
rowObj.AddPair('DCallType', ApiDB.uqComplaintHistoryDCALLTYPE.AsString);
Inc(returnedCount);
Next;
......
......@@ -3,7 +3,7 @@ unit Common.Config;
interface
const
defaultServerUrl = 'http://localhost:2009/emiMobile/';
defaultServerUrl = 'http://0.0.0.0:2009/emiMobile/';
type
TServerConfig = class
......
......@@ -186,6 +186,6 @@ begin
iniFile.Free;
end;
Logger.AddAppender(TMemoLogAppender.Create( memoLogLevel, FMain.memoinfo ));
Logger.AddAppender(TFileLogAppender.Create( fileLogLevel, 'webPoliceReports' ));
Logger.AddAppender(TFileLogAppender.Create( fileLogLevel, 'emiMobileServer' ));
Application.Run;
end.
......@@ -104,7 +104,7 @@
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<DCC_ExeOutput>.</DCC_ExeOutput>
<DCC_ExeOutput>.\bin</DCC_ExeOutput>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>3</VerInfo_Release>
......@@ -196,12 +196,7 @@
<Source>
<Source Name="MainSource">emiMobileServer.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k290.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp290.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k290.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp290.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>
<Excluded_Packages/>
</Delphi.Personality>
<Deployment Version="5">
<DeployFile LocalName="emiMobileServer.exe" Configuration="Debug" Class="ProjectOutput">
......
{
"AuthUrl" : "http://localhost:2009/emimobile/auth/",
"ApiUrl" : "http://localhost:2009/emimobile/api/",
"AppUrl" : "http://localhost:2009/emimobile/app/"
}
\ No newline at end of file
.login-card {
display: inline-block;
width: 300px; /* Adjust width as needed */
padding: 0;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
.card-header {
width: 100%;
text-align: left; /* Align text to the left */
background-color: #f8f9fa; /* Match the card background */
padding: 0.75rem 1.25rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
border-top-left-radius: 10px;
border-top-right-radius: 10px;
margin: 0; /* Remove any margin */
box-sizing: border-box; /* Ensure padding is included in the element's total width and height */
}
.mr-2 {
margin-right: 0.5rem;
}
.card-title {
margin: 0;
font-size: 1.25rem; /* Adjust font size as needed */
}
.card-body {
padding: 2rem;
}
.table tbody tr:hover {
background-color: #d1e7fd; /* Light blue color for hover effect */
cursor: pointer;
}
.form-input{
display: table;
}
.form-cells{
display: table-cell
}
.table tbody tr {
transition: background-color 0.3s ease;
}
.table {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
@media (max-width: 1200px) {
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.table thead {
display: none;
}
.table tbody, .table tr, .table td {
display: block;
width: 100%;
}
.table tr {
margin-bottom: 1rem;
}
.table td {
text-align: right;
padding-left: 50%; /* Adjust padding to accommodate the data-label */
position: relative;
}
.table td::before {
content: attr(data-label);
position: absolute;
left: 0;
width: 50%;
padding-left: 15px; /* Adjust as necessary */
font-weight: bold;
text-align: left;
}
.table td .transcript {
margin-top: 20px; /* Set top margin to 20px */
text-align: left; /* Ensure text alignment is left */
margin-left: 8px;
white-space: normal; /* Prevent text from being cut off */
}
}
.login-navbar {
max-width: 1200px; /* Set the max-width to match a medium screen */
margin: auto;
border-bottom-left-radius: 10px; /* Round the bottom left corner */
border-bottom-right-radius: 10px; /* Round the bottom right corner */
border: 1px solid #d3d3d3;
}
.navbar-toggler {
display: none;
}
.dropdown-menu a {
display: flex; /* Use flexbox for alignment */
align-items: center; /* Vertically center the content */
width: 100%; /* Ensure they take up the full width */
padding: 0.5rem 1rem; /* Add padding to make them clickable */
color: #000; /* Adjust the text color if necessary */
text-decoration: none; /* Remove underlines */
}
.dropdown-menu a:hover {
background-color: #204d74;
color: #fff;
}
.dropdown-menu a span {
flex-grow: 1; /* Make the span take up the remaining space */
}
/* Style for the selected number */
.selected-number .page-link {
background-color: #204d74;
color: #fff !important;
}
/* Style for the unselected numbers and text (previous/next) */
.pagination .page-item a,
.pagination .page-item span {
color: #204d74;
}
.pagination .page-item.active .page-link,
.pagination .page-item.active .page-link:hover,
.pagination .page-item.active .page-link:focus {
background-color: #204d74;
border-color: #204d74;
color: #fff !important;
}
/* This is needed to get rid of the line that was appearing. */
span.card {
border: none;
}
.modal-backdrop {
z-index: 1040 !important;
}
.modal {
z-index: 1055 !important;
}
object FViewComplaintArchive: TFViewComplaintArchive
Width = 640
Height = 480
object tblRemarksArc: TWebDBTableControl
Left = 102
Top = 128
Width = 191
Height = 147
ElementId = 'tbl_remarks_arc'
BorderColor = clSilver
ChildOrder = 8
ElementFont = efCSS
ElementHeaderClassName = 'table-light'
ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control'
Footer.InputElementClassName = 'form-control'
Footer.LinkActiveElementClassName = 'link-primary'
Footer.LinkElementClassName = 'link-secondary'
Footer.ListElementClassName = 'pagination'
Footer.ListItemElementClassName = 'page-item'
Footer.ListLinkElementClassName = 'page-link'
Header.ButtonActiveElementClassName = 'btn btn-primary'
Header.ButtonElementClassName = 'btn btn-light'
Header.DropDownElementClassName = 'form-control'
Header.InputElementClassName = 'form-control'
Header.LinkActiveElementClassName = 'link-primary'
Header.LinkElementClassName = 'link-secondary'
Header.ListElementClassName = 'pagination'
Header.ListItemElementClassName = 'page-item'
Header.ListLinkElementClassName = 'page-link'
WordWrap = True
Columns = <
item
ElementClassName = 'd-none'
DataField = 'MemoType'
Title = 'Type'
TitleElementClassName = 'd-none'
end
item
DataField = 'Timestamp'
Title = 'Timestamp'
end
item
DataField = 'Remarks'
Title = 'Remarks'
end>
DataSource = wdsRemarksArc
end
object btnUntArc: TWebButton
Left = 378
Top = 85
Width = 96
Height = 25
Caption = 'UNT'
ChildOrder = 1
ElementID = 'btn_unt_arc'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnUntArcClick
end
object btnREMArc: TWebButton
Left = 276
Top = 85
Width = 96
Height = 25
Caption = 'REM'
ChildOrder = 1
ElementID = 'btn_rem_arc'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnREMArcClick
end
object btnE911Arc: TWebButton
Left = 166
Top = 85
Width = 96
Height = 25
Caption = 'E-911'
ChildOrder = 1
ElementID = 'btn_e911_arc'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnE911ArcClick
end
object btnCmpArc: TWebButton
Left = 64
Top = 85
Width = 96
Height = 25
Caption = 'CMP'
ChildOrder = 1
ElementID = 'btn_cmp_arc'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnCmpArcClick
end
object btnComplaintViewOnMapArc: TWebButton
Left = 342
Top = 259
Width = 96
Height = 25
Caption = 'Map'
ChildOrder = 1
ElementID = 'btn_complaint_view_on_map_arc'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnComplaintViewOnMapArcClick
end
object xdwcComplaintArchive: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 292
Top = 310
end
object xdwdsRemarksArc: TXDataWebDataSet
Left = 68
Top = 316
object xdwdsRemarksArcMemoId: TStringField
FieldName = 'MemoId'
end
object xdwdsRemarksArcCFSId: TStringField
FieldName = 'CFSId'
end
object xdwdsRemarksArcMemoType: TStringField
FieldName = 'MemoType'
end
object xdwdsRemarksArcTimestamp: TStringField
FieldName = 'Timestamp'
end
object xdwdsRemarksArcBadgeNumber: TStringField
FieldName = 'BadgeNumber'
end
object xdwdsRemarksArcRemarks: TStringField
FieldName = 'Remarks'
end
end
object wdsRemarksArc: TWebDataSource
DataSet = xdwdsRemarksArc
Left = 156
Top = 314
end
end
<div class="d-flex flex-column h-100 w-100 overflow-hidden">
<div class="flex-grow-1 d-flex flex-column overflow-auto bg-light p-2 p-md-3" style="min-height:0;">
<!-- Sticky block: Summary header + summary body -->
<div class="sticky-top bg-light" style="z-index:20;">
<!-- Summary header -->
<div class="card border-0 shadow-sm mb-2">
<div class="card-header bg-white py-2">
<button
class="btn btn-link text-decoration-none p-0 w-100 d-flex align-items-center justify-content-between summary-toggle"
type="button"
data-bs-toggle="collapse"
data-bs-target="#cdetails_summary_arc"
aria-expanded="true"
aria-controls="cdetails_summary_arc">
<span class="fw-semibold text-dark" id="lbl_summary_title_arc">Summary</span>
<span class="summary-chevron" aria-hidden="true">
<svg class="summary-chevron-icon" viewBox="0 0 16 16" focusable="false">
<path fill="currentColor" d="M7.646 5.354a.5.5 0 0 1 .708 0l5 5a.5.5 0 0 1-.708.708L8 6.414 3.354 11.06a.5.5 0 1 1-.708-.708l5-5z"/>
</svg>
</span>
</button>
</div>
</div>
<!-- Summary body -->
<div id="cdetails_summary_arc" class="collapse show">
<div class="card border-0 shadow-sm mb-2">
<div class="card-body py-2">
<table class="table table-sm mb-0 w-100">
<tbody>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Priority:</th>
<td class="w-100" id="lbl_priority_arc"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Status:</th>
<td class="w-100" id="lbl_status_arc"></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_arc"></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_arc"></td>
</tr>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Address:</th>
<td class="w-100" id="lbl_address_arc"></td>
</tr>
<tr id="row_business_arc" class="d-none">
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Business:</th>
<td class="w-100" id="lbl_business_arc"></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Remarks toggle filters -->
<div class="pb-2">
<div class="d-flex flex-wrap gap-2 justify-content-center mt-2" id="row_remarks_toggles_arc">
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_cmp_arc" aria-pressed="true">CMP</button>
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_e911_arc" aria-pressed="true">E-911</button>
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_rem_arc" aria-pressed="true">REM</button>
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_unt_arc" aria-pressed="true">UNT</button>
</div>
</div>
</div>
</div>
<!-- Remarks table (scrolls with page) -->
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div id="tbl_remarks_arc"></div>
</div>
</div>
</div>
<button id="btn_complaint_view_on_map_arc"
type="button"
class="btn btn-primary btn-sm shadow position-fixed"
style="right: 15px; bottom: 25px; z-index: 1040;">
Map
</button>
</div>
unit View.ComplaintArchive;
interface
uses
System.SysUtils, System.Classes, JS, Web,
WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls, Data.DB,
WEBLib.StdCtrls, WEBLib.Grids, WEBLib.DBCtrls, WEBLib.DB,
XData.Web.Client, XData.Web.JsonDataset, XData.Web.Dataset,
ConnectionModule;
type
TFViewComplaintArchive = class(TWebForm)
tblRemarksArc: TWebDBTableControl;
xdwcComplaintArchive: TXDataWebClient;
xdwdsRemarksArc: TXDataWebDataSet;
xdwdsRemarksArcMemoId: TStringField;
xdwdsRemarksArcCFSId: TStringField;
xdwdsRemarksArcMemoType: TStringField;
xdwdsRemarksArcTimestamp: TStringField;
xdwdsRemarksArcBadgeNumber: TStringField;
xdwdsRemarksArcRemarks: TStringField;
wdsRemarksArc: TWebDataSource;
btnUntArc: TWebButton;
btnREMArc: TWebButton;
btnE911Arc: TWebButton;
btnCmpArc: TWebButton;
btnComplaintViewOnMapArc: TWebButton;
procedure WebFormCreate(Sender: TObject);
procedure btnCmpArcClick(Sender: TObject);
procedure btnE911ArcClick(Sender: TObject);
procedure btnREMArcClick(Sender: TObject);
procedure btnUntArcClick(Sender: TObject);
procedure btnComplaintViewOnMapArcClick(Sender: TObject);
private
FComplaintId: string;
FCfsId: string;
FAllMemos: TJSArray;
FShowCMP: Boolean;
FShowE911: Boolean;
FShowREM: Boolean;
FShowUNT: Boolean;
procedure WireUi;
procedure SetTextById(const Id, Value: string);
procedure SetHiddenById(const Id: string; Hidden: Boolean);
procedure SetButtonActive(aButton: TWebButton; Active: Boolean);
[async] procedure LoadArchiveAsync;
[async] procedure LoadMemosAsync;
procedure ApplyMemoFilters;
function GetMemoTypeCode(const MemoType: string): string;
function MemoTypeLabel(const MemoType: string): string;
public
class function CreateForm(const AHostId, AComplaintId: string): TFViewComplaintArchive;
end;
implementation
uses
View.Main;
{$R *.dfm}
class function TFViewComplaintArchive.CreateForm(const AHostId, AComplaintId: string): TFViewComplaintArchive;
begin
Result := TFViewComplaintArchive(inherited CreateNew(AHostId));
Result.FComplaintId := AComplaintId;
end;
procedure TFViewComplaintArchive.WebFormCreate(Sender: TObject);
begin
FAllMemos := nil;
FShowCMP := True;
FShowE911 := True;
FShowREM := True;
FShowUNT := True;
WireUi;
LoadArchiveAsync;
end;
procedure TFViewComplaintArchive.WireUi;
begin
// Nothing required here beyond component OnClick handlers;
// all element IDs are bound via the DFM.
end;
procedure TFViewComplaintArchive.SetTextById(const Id, Value: string);
var
el: TJSElement;
begin
el := Document.getElementById(Id);
if el <> nil then
el.innerHTML := Value;
end;
procedure TFViewComplaintArchive.SetHiddenById(const Id: string; Hidden: Boolean);
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(Document.getElementById(Id));
if el = nil then
Exit;
if Hidden then
el.classList.add('d-none')
else
el.classList.remove('d-none');
end;
procedure TFViewComplaintArchive.SetButtonActive(aButton: TWebButton; Active: Boolean);
var
el: TJSHTMLElement;
begin
if (aButton = nil) or (aButton.ElementHandle = nil) then
Exit;
el := TJSHTMLElement(aButton.ElementHandle);
if Active then
begin
el.classList.add('active');
el.setAttribute('aria-pressed', 'true');
end
else
begin
el.classList.remove('active');
el.setAttribute('aria-pressed', 'false');
end;
end;
[async] procedure TFViewComplaintArchive.LoadArchiveAsync;
var
resp: TXDataClientResponse;
rootObj: TJSObject;
dataObj: TJSObject;
business: string;
begin
resp := await(xdwcComplaintArchive.RawInvokeAsync('IApiService.GetComplaintArchiveDetails', [FComplaintId]));
rootObj := TJSObject(resp.Result);
dataObj := TJSObject(rootObj['data']);
// Summary title (optional)
SetTextById('lbl_summary_title_arc', 'Summary');
SetTextById('lbl_priority_arc', string(dataObj['Priority']));
SetTextById('lbl_dispatch_code_arc', string(dataObj['DispatchCodeDesc']));
SetTextById('lbl_dispatch_district_arc', string(dataObj['DispatchDistrict']));
SetTextById('lbl_address_arc', string(dataObj['Address']));
business := string(dataObj['Business']);
if business <> '' then
begin
SetTextById('lbl_business_arc', business);
SetHiddenById('row_business_arc', False);
end
else
SetHiddenById('row_business_arc', True);
// Status: same logic style as details; simplest is derive from timestamps if you want,
// but archive view can just show blank unless you prefer the full logic.
// If your archive endpoint includes a computed Status, you can display it directly.
if dataObj.hasOwnProperty('Status') then
SetTextById('lbl_status_arc', string(dataObj['Status']))
else
SetTextById('lbl_status_arc', '');
// CFSId is needed for memos
FCfsId := string(dataObj['CFSId']);
await(LoadMemosAsync);
end;
function TFViewComplaintArchive.MemoTypeLabel(const MemoType: string): string;
var
c: string;
begin
c := GetMemoTypeCode(MemoType);
if c = 'CMP' then Exit('CMP');
if c = 'E911' then Exit('E-911');
if c = 'REM' then Exit('REM');
if c = 'UNT' then Exit('UNT');
Result := MemoType;
end;
function TFViewComplaintArchive.GetMemoTypeCode(const MemoType: string): string;
var
mt: string;
begin
mt := LowerCase(Trim(MemoType));
// Match whatever your server returns in GetComplaintMemos:
// Keep these mappings aligned with ComplaintDetails.
if (mt = 'cmp') or (mt = 'complaint') then Exit('CMP');
if (mt = 'e-911') or (mt = 'e911') then Exit('E911');
if (mt = 'rem') or (mt = 'remarks') then Exit('REM');
if (mt = 'unt') or (mt = 'unit') then Exit('UNT');
Result := UpperCase(mt);
end;
[async] procedure TFViewComplaintArchive.LoadMemosAsync;
var
resp: TXDataClientResponse;
rootObj: TJSObject;
dataArr: TJSArray;
i: Integer;
memoObj: TJSObject;
memoType: string;
begin
// Reset dataset
xdwdsRemarksArc.Close;
resp := await(xdwcComplaintArchive.RawInvokeAsync('IApiService.GetComplaintMemos', [FCfsId]));
rootObj := TJSObject(resp.Result);
dataArr := TJSArray(rootObj['data']);
// Save master array for filtering
FAllMemos := dataArr;
// Add/normalize memo type label/code (client-side)
for i := 0 to dataArr.length - 1 do
begin
memoObj := TJSObject(dataArr[i]);
memoType := string(memoObj['MemoType']);
memoObj['MemoTypeCode'] := GetMemoTypeCode(memoType);
memoObj['MemoType'] := MemoTypeLabel(memoType);
end;
ApplyMemoFilters;
end;
procedure TFViewComplaintArchive.ApplyMemoFilters;
var
filtered: TJSArray;
i: Integer;
memoObj: TJSObject;
code: string;
ok: Boolean;
begin
if FAllMemos = nil then
Exit;
filtered := TJSArray.new;
for i := 0 to FAllMemos.length - 1 do
begin
memoObj := TJSObject(FAllMemos[i]);
code := string(memoObj['MemoTypeCode']);
ok := False;
if (code = 'CMP') and FShowCMP then ok := True;
if (code = 'E911') and FShowE911 then ok := True;
if (code = 'REM') and FShowREM then ok := True;
if (code = 'UNT') and FShowUNT then ok := True;
if ok then
filtered.push(memoObj);
end;
xdwdsRemarksArc.SetJsonData(filtered);
xdwdsRemarksArc.Open;
end;
procedure TFViewComplaintArchive.btnCmpArcClick(Sender: TObject);
begin
FShowCMP := not FShowCMP;
SetButtonActive(btnCmpArc, FShowCMP);
ApplyMemoFilters;
end;
procedure TFViewComplaintArchive.btnE911ArcClick(Sender: TObject);
begin
FShowE911 := not FShowE911;
SetButtonActive(btnE911Arc, FShowE911);
ApplyMemoFilters;
end;
procedure TFViewComplaintArchive.btnREMArcClick(Sender: TObject);
begin
FShowREM := not FShowREM;
SetButtonActive(btnREMArc, FShowREM);
ApplyMemoFilters;
end;
procedure TFViewComplaintArchive.btnUntArcClick(Sender: TObject);
begin
FShowUNT := not FShowUNT;
SetButtonActive(btnUntArc, FShowUNT);
ApplyMemoFilters;
end;
procedure TFViewComplaintArchive.btnComplaintViewOnMapArcClick(Sender: TObject);
begin
if Assigned(FViewMain) then
FViewMain.ShowMapFocusComplaint(FComplaintId);
end;
end.
object FViewComplaintDetails: TFViewComplaintDetails
Width = 800
Height = 672
Caption = 'tbl_logs'
CSSLibrary = cssBootstrap
ElementFont = efCSS
object btnHistory: TWebButton
......@@ -361,8 +360,8 @@ object FViewComplaintDetails: TFViewComplaintDetails
ListSource = wdsWarnings
end
object btnComplaintViewOnMap: TWebButton
Left = 510
Top = 430
Left = 687
Top = 496
Width = 96
Height = 25
Caption = 'Map'
......@@ -376,12 +375,12 @@ object FViewComplaintDetails: TFViewComplaintDetails
end
object xdwcComplaintDetails: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 378
Top = 424
Left = 384
Top = 422
end
object xdwdsRemarks: TXDataWebDataSet
Left = 40
Top = 350
Top = 348
object xdwdsRemarksMemoId: TStringField
FieldName = 'MemoId'
end
......@@ -403,12 +402,15 @@ object FViewComplaintDetails: TFViewComplaintDetails
end
object wdsRemarks: TWebDataSource
DataSet = xdwdsRemarks
Left = 126
Top = 350
Left = 128
Top = 348
end
object xdwdsHistory: TXDataWebDataSet
Left = 238
Top = 348
object xdwdsHistoryComplaintId: TStringField
FieldName = 'ComplaintId'
end
object xdwdsHistoryComplaint: TStringField
FieldName = 'Complaint'
end
......@@ -428,11 +430,11 @@ object FViewComplaintDetails: TFViewComplaintDetails
object wdsHistory: TWebDataSource
DataSet = xdwdsHistory
Left = 328
Top = 350
Top = 348
end
object xdwdsContacts: TXDataWebDataSet
Left = 436
Top = 352
Left = 438
Top = 348
object xdwdsContactsName: TStringField
FieldName = 'Name'
end
......@@ -448,12 +450,12 @@ object FViewComplaintDetails: TFViewComplaintDetails
end
object wdsContacts: TWebDataSource
DataSet = xdwdsContacts
Left = 530
Top = 352
Left = 532
Top = 348
end
object xdwdsWarnings: TXDataWebDataSet
Left = 634
Top = 492
Left = 612
Top = 422
object xdwdsWarningsCodeDesc: TStringField
FieldName = 'CodeDesc'
end
......@@ -466,7 +468,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
end
object wdsWarnings: TWebDataSource
DataSet = xdwdsWarnings
Left = 728
Top = 492
Left = 712
Top = 422
end
end
......@@ -97,8 +97,8 @@
<button id="btn_complaint_view_on_map"
type="button"
class="btn btn-primary btn-sm shadow position-fixed"
style="right: 12px; bottom: 72px; z-index: 1040;">
View On Map
style="right: 15px; bottom: 25px; z-index: 1040;">
Map
</button>
</div>
......@@ -54,6 +54,7 @@ type
btnUnt: TWebButton;
lstWarnings: TWebDBListControl;
btnComplaintViewOnMap: TWebButton;
xdwdsHistoryComplaintId: TStringField;
procedure btnRemarksClick(Sender: TObject);
procedure btnHistoryClick(Sender: TObject);
......@@ -560,6 +561,7 @@ begin
rootObj := TJSObject(resp.Result);
dataObj := TJSObject(rootObj['data']);
complaintText := string(dataObj['Complaint']);
priorityText := string(dataObj['Priority']);
dispatchDescText := string(dataObj['DispatchCodeDesc']);
......
......@@ -56,8 +56,8 @@ object FViewComplaints: TFViewComplaints
'ustify-content-center gap-1"><button type="button" class="btn bt' +
'n-primary btn-sm complaint-details-btn" data-id="(%ComplaintId%)' +
'">Details</button><button type="button" class="btn btn-primary b' +
'tn-sm btn-complaint-map" data-id="(%ComplaintId%)">View On Map</' +
'button></div></div></div>'
'tn-sm btn-complaint-map" data-id="(%ComplaintId%)">Map</button><' +
'/div></div></div>'
ListSource = wdsComplaints
end
object xdwcComplaints: TXDataWebClient
......@@ -121,10 +121,4 @@ object FViewComplaints: TFViewComplaints
Left = 156
Top = 410
end
object tmrRefresh: TWebTimer
Interval = 30000
OnTimer = tmrRefreshTimer
Left = 164
Top = 44
end
end
......@@ -31,20 +31,19 @@ type
xdwdsComplaintsPriorityColor: TStringField;
xdwdsComplaintsPriorityTextColor: TStringField;
xdwdsComplaintsDistrictSector: TStringField;
tmrRefresh: TWebTimer;
xdwdsComplaintsBusiness: TStringField;
procedure WebFormCreate(Sender: TObject);
procedure btnRefreshClick(Sender: TObject);
procedure tmrRefreshTimer(Sender: TObject);
procedure WebFormDestroy(Sender: TObject);
private
FSelectProc: TSelectProc;
FLoading: Boolean;
FFirstLoad: Boolean;
[async] procedure GetComplaints;
procedure HandleListClick(e: TJSMouseEvent);
procedure ShowHideBusinessRows;
public
property OnShowDetails: TSelectProc read FSelectProc write FSelectProc;
procedure RefreshData;
end;
var
......@@ -57,19 +56,12 @@ implementation
procedure TFViewComplaints.WebFormCreate(Sender: TObject);
begin
Document.addEventListener('click', @HandleListClick);
ShowSpinner('spinner');
tmrRefresh.Enabled := False;
FFirstLoad := True;
GetComplaints;
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);
}
pas['View.Main'].FViewMain.ShowComplaintDetails(id);
};
}
end;
......@@ -139,18 +131,12 @@ begin
end;
end;
procedure TFViewComplaints.WebFormDestroy(Sender: TObject);
begin
Document.removeEventListener('click', @HandleListClick);
end;
//Note: HTML for individual complaint cards can be found in the twebdblistcontrol HTMLString property
procedure TFViewComplaints.btnRefreshClick(Sender: TObject);
begin
GetComplaints;
end;
[async] procedure TFViewComplaints.GetComplaints;
var
xdcResponse: TXDataClientResponse;
......@@ -161,7 +147,9 @@ begin
Exit;
FLoading := True;
ShowSpinner('spinner');
if FFirstLoad then
ShowSpinner('spinner');
try
try
xdcResponse := await(xdwcComplaints.RawInvokeAsync('IApiService.GetComplaintList', []));
......@@ -182,13 +170,18 @@ begin
Utils.ShowErrorModal(E.Message);
end;
finally
HideSpinner('spinner');
FLoading := False;
if FFirstLoad then
begin
HideSpinner('spinner');
FFirstLoad := False;
end;
end;
end;
procedure TFViewComplaints.tmrRefreshTimer(Sender: TObject);
procedure TFViewComplaints.RefreshData;
begin
Console.Log('Complaints.RefreshData');
GetComplaints;
end;
......
......@@ -76,7 +76,6 @@ procedure TFViewEditUser.btnCancelClick(Sender: TObject);
// Cancels the edit or addition
begin
Info := 'Failure:Changes discarded!';
FViewMain.ShowUserForm(Info);
end;
procedure TFViewEditUser.btnCloseNotificationClick(Sender: TObject);
......@@ -222,7 +221,6 @@ begin
if (not Info.Contains('Failure')) then
begin
console.log('Navigating back to user list...');
FViewMain.ShowUserForm(Info);
end
else
begin
......
object FViewMain: TFViewMain
Width = 640
Height = 586
Width = 1208
Height = 810
CSSLibrary = cssBootstrap
ElementFont = efCSS
OnCreate = WebFormCreate
......@@ -16,18 +16,6 @@ object FViewMain: TFViewMain
Visible = False
WidthPercent = 100.000000000000000000
end
object wllblUserProfile: TWebLinkLabel
Left = 529
Top = 21
Width = 63
Height = 15
ElementID = 'dropdown.menu.userprofile'
Visible = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = wllblUserProfileClick
Caption = ' User Profile'
end
object wllblLogout: TWebLinkLabel
Left = 551
Top = 85
......@@ -61,7 +49,6 @@ object FViewMain: TFViewMain
Visible = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = lblCallsListClick
Caption = 'Calls'
end
object lblUsers: TWebLinkLabel
......@@ -74,7 +61,6 @@ object FViewMain: TFViewMain
Visible = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = lblUsersClick
Caption = 'Users'
end
object lblMainTitle: TWebLabel
......@@ -88,12 +74,12 @@ object FViewMain: TFViewMain
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebPanel1: TWebPanel
object pnlMain: TWebPanel
Left = 136
Top = 110
Width = 471
Height = 369
ElementID = 'main.webpanel'
ElementID = 'pnl_main'
ChildOrder = 3
TabOrder = 0
end
......@@ -108,7 +94,7 @@ object FViewMain: TFViewMain
end
object WebMemo1: TWebMemo
Left = 136
Top = 467
Top = 469
Width = 471
Height = 83
ElementID = 'main.debugmemo'
......@@ -121,8 +107,8 @@ object FViewMain: TFViewMain
WidthPercent = 100.000000000000000000
end
object btnMap: TWebButton
Left = 148
Top = 58
Left = 212
Top = 580
Width = 59
Height = 25
Caption = 'Map'
......@@ -135,8 +121,8 @@ object FViewMain: TFViewMain
OnClick = btnMapClick
end
object btnComplaints: TWebButton
Left = 213
Top = 58
Left = 285
Top = 580
Width = 68
Height = 25
Caption = 'Complaints'
......@@ -149,8 +135,8 @@ object FViewMain: TFViewMain
OnClick = btnComplaintsClick
end
object btnUnits: TWebButton
Left = 294
Top = 58
Left = 364
Top = 580
Width = 59
Height = 25
Caption = 'Units'
......@@ -162,6 +148,55 @@ object FViewMain: TFViewMain
WidthPercent = 100.000000000000000000
OnClick = btnUnitsClick
end
object pnlMap: TWebPanel
Left = 708
Top = 99
Width = 237
Height = 436
ElementID = 'pnl_map'
ChildOrder = 3
TabOrder = 6
end
object pnlUnits: TWebPanel
Left = 740
Top = 180
Width = 227
Height = 139
ElementID = 'pnl_units'
ChildOrder = 3
TabOrder = 7
end
object pnlComplaints: TWebPanel
Left = 740
Top = 346
Width = 227
Height = 145
ElementID = 'pnl_complaints'
ChildOrder = 3
TabOrder = 8
end
object pnlDetails: TWebPanel
Left = 992
Top = 277
Width = 163
Height = 114
ElementID = 'pnl_details'
ChildOrder = 15
ElementFont = efCSS
TabOrder = 9
object btnDetailsModalClose: TWebButton
Left = 127
Top = 8
Width = 28
Height = 23
ElementID = 'btn_details_modal_close'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnDetailsModalCloseClick
end
end
object xdwcBadgeCounts: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 44
......@@ -173,4 +208,10 @@ object FViewMain: TFViewMain
Left = 42
Top = 360
end
object tmrGlobalRefresh: TWebTimer
Interval = 30000
OnTimer = tmrGlobalRefreshTimer
Left = 42
Top = 434
end
end
......@@ -13,16 +13,33 @@
<!-- Right: Connection label -->
<span id="view.main.lblconnection" class="navbar-text text-light ms-auto"></span>
</div>
</div>
</nav>
<!-- Main content: fills space between navbars -->
<main id="main.webpanel" class="flex-grow-1 position-relative p-0 overflow-hidden" style="min-height:0;">
<!-- TWebPanel content gets injected here -->
</main>
<div id="pnl_main" class="flex-grow-1 position-relative p-0 overflow-hidden d-none" style="min-height:0;"></div>
<div id="pnl_map" class="flex-grow-1 position-relative p-0 overflow-hidden" style="min-height:0;"></div>
<div id="pnl_units" class="flex-grow-1 position-relative p-0 overflow-hidden d-none" style="min-height:0;"></div>
<div id="pnl_complaints" class="flex-grow-1 position-relative p-0 overflow-hidden d-none" style="min-height:0;"></div>
<!-- Details modal (new panel pnl_details) -->
<div id="pnl_details"
class="position-fixed top-0 start-0 w-100 h-100 d-none d-flex justify-content-center align-items-center"
style="background: rgba(0,0,0,0.5); z-index:1060;">
<div class="card shadow-lg w-100 mx-2 d-flex flex-column" style="max-width: 98vw; height: 96%;">
<div class="card-header d-flex align-items-center justify-content-between">
<h5 class="card-title mb-0" id="lbl_details_title">Details</h5>
<button id="btn_details_modal_close" type="button" class="btn-close" aria-label="Close"></button>
</div>
<div class="card-body p-0 flex-grow-1 overflow-hidden" style="min-height:0;">
<div id="pnl_details_host" class="h-100"></div>
</div>
</div>
</div>
<!-- Bottom Nav -->
<nav class="navbar navbar-dark bg-primary py-2 flex-shrink-0">
<nav id="bottom_nav" class="navbar navbar-dark bg-primary py-2 flex-shrink-0">
<div class="container-fluid">
<div class="d-flex justify-content-center gap-3 w-100">
<button id="view.main.btnmap" type="button" class="btn btn-primary">
......@@ -86,4 +103,3 @@
</div>
</div>
</div>
......@@ -72,17 +72,11 @@ object FViewMap: TFViewMap
Left = 232
Top = 696
end
object tmrRefresh: TWebTimer
Interval = 30000
OnTimer = tmrRefreshTimer
Left = 358
Top = 696
end
object tmrLocate: TWebTimer
Enabled = False
Interval = 100
OnTimer = tmrLocateTimer
Left = 174
Top = 74
Left = 222
Top = 52
end
end
......@@ -23,7 +23,7 @@
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="map_filter_location">
<label class="form-check-label" for="map_filter_location">Show Location</label>
<label class="form-check-label" for="map_filter_location">Show My Location</label>
</div>
</div>
<div class="d-grid gap-2 mt-4">
......@@ -50,7 +50,6 @@
class="btn btn-primary border shadow position-absolute bottom-0 end-0 me-2 mb-4"
style="z-index:1000;">
<i class="fa fa-crosshairs"></i>
<span class="d-none d-sm-inline">Locate</span>
</button>
<!-- Filters (top-right) -->
......
......@@ -16,7 +16,6 @@ type
lfMap: TTMSFNCLeaflet;
httpReqGeoJson: TWebHttpRequest;
xdwcMap: TXDataWebClient;
tmrRefresh: TWebTimer;
btnFindLocation: TWebButton;
tmrLocate: TWebTimer;
......@@ -25,7 +24,6 @@ type
procedure lfMapCustomizeMarker(Sender: TObject;
var ACustomizeMarker: string);
procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
procedure tmrRefreshTimer(Sender: TObject);
procedure btnFindLocationClick(Sender: TObject);
procedure tmrLocateTimer(Sender: TObject);
private
......@@ -50,6 +48,7 @@ type
public
procedure FocusUnit(const unitId: string);
procedure FocusComplaint(const complaintId: string);
procedure RefreshData;
end;
var
......@@ -70,20 +69,16 @@ begin
httpReqGeoJson.Execute;
asm
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);
}
};
window.showUnitDetails = function (id) {
console.log('JS bridge showUnitDetails called, id=', id);
try {
pas['View.Main'].FViewMain.ShowUnitDetails(id);
} catch (e) {
console.log('Error in TFViewMain.ShowUnitDetails', e);
}
};
end;
......@@ -155,9 +150,7 @@ begin
try
lfMap.Polygons.Clear;
Console.Log('GeoJSON len=' + AResponse.Length.ToString);
lfMap.LoadGeoJSONFromText(AResponse, True, Trim(FPendingUnitId) = '');
Console.Log('Loaded polygons count=' + lfMap.Polygons.Count.ToString);
for i := 0 to lfMap.Polygons.Count - 1 do
begin
......@@ -290,7 +283,6 @@ begin
// --- Swap Markers (no blank map while loading) ---------------------------
lfMap.BeginUpdate;
try
// Delete old unit/complaint markers right before adding new ones
for i := lfMap.Markers.Count - 1 downto 0 do
begin
m := lfMap.Markers[i];
......@@ -299,7 +291,7 @@ begin
lfMap.Markers.Delete(i);
end;
// Add unit markers
// Unit markers
if unitsData <> nil then
begin
for i := 0 to unitsData.Length - 1 do
......@@ -404,12 +396,11 @@ begin
'</div>';
m.DataString := 'unit|' + unitId;
console.log('Unit marker ds=' + m.DataString);
m.IconURL := CarIconForDistrict(dist);
end;
end;
// Add complaint markers
// Complaint markers
if complaintsData <> nil then
begin
for i := 0 to complaintsData.Length - 1 do
......@@ -645,14 +636,6 @@ begin
FDoFocusZoom := False;
end;
procedure TFViewMap.tmrRefreshTimer(Sender: TObject);
begin
if FLoadingPoints then
Exit;
LoadPointsAsync(False);
end;
procedure TFViewMap.btnFindLocationClick(Sender: TObject);
var
coord: TTMSFNCMapsCoordinateRec;
......@@ -662,6 +645,13 @@ begin
coord := CreateCoordinate(userLocationMarker.Latitude, userLocationMarker.Longitude);
lfMap.SetCenterCoordinate(coord);
FPendingFocusCoord := coord;
FPendingFocusZoom := 17;
FDoFocusZoom := True;
tmrLocate.Interval := 250;
tmrLocate.Enabled := True;
end;
......@@ -758,6 +748,12 @@ begin
end;
procedure TFViewMap.RefreshData;
begin
Console.Log('Map.RefreshData');
LoadPointsAsync(False);
end;
end.
......@@ -95,7 +95,7 @@
<button id="btn_unit_view_on_map"
type="button"
class="btn btn-primary btn-sm shadow position-fixed"
style="right: 12px; bottom: 72px; z-index: 1040;">
style="right: 15px; bottom: 25px; z-index: 1040;">
Map
</button>
</div>
......
......@@ -53,7 +53,7 @@ object FViewUnits: TFViewUnits
'pe="button" class="btn btn-primary btn-sm btn-unit-details" data' +
'-unitid="(%UnitId%)">Details</button> <button type="button"' +
' class="btn btn-primary btn-sm btn-unit-map" data-unitid="(%Unit' +
'Id%)">View On Map</button> </div> </div></div>'
'Id%)">Map</button> </div> </div></div>'
ListSource = wdsUnits
end
object wdsUnits: TWebDataSource
......@@ -96,10 +96,4 @@ object FViewUnits: TFViewUnits
Left = 58
Top = 410
end
object tmrRefresh: TWebTimer
Interval = 30000
OnTimer = tmrRefreshTimer
Left = 172
Top = 22
end
end
......@@ -25,16 +25,14 @@ type
xdwdsUnitsOfficer1: TStringField;
xdwdsUnitsOfficer2: TStringField;
xdwdsUnitsCallType: TStringField;
tmrRefresh: TWebTimer;
procedure WebFormCreate(Sender: TObject);
procedure btnRefreshClick(Sender: TObject);
procedure tmrRefreshTimer(Sender: TObject);
private
FLoading: Boolean;
FFirstLoad: Boolean;
[async] procedure GetUnits;
procedure HandleListClick(e: TJSMouseEvent);
public
procedure RefreshData;
end;
var
......@@ -49,19 +47,13 @@ procedure TFViewUnits.WebFormCreate(Sender: TObject);
begin
DMConnection.ApiConnection.Connected := True;
Document.addEventListener('click', @HandleListClick);
tmrRefresh.Enabled := False;
FFirstLoad := True;
GetUnits;
tmrRefresh.Enabled := True;
asm
if (!window.showUnitDetails) {
window.showUnitDetails = function (id) {
console.log('JS bridge showUnitDetails called, id=', id);
try {
pas['View.Main'].FViewMain.ShowUnitDetails(id);
} catch (e) {
console.log('Error in TFViewMain.ShowUnitDetails', e);
}
pas['View.Main'].FViewMain.ShowUnitDetails(id);
};
}
end;
......@@ -106,68 +98,56 @@ begin
e.stopPropagation;
asm
try {
pas['View.Main'].FViewMain.ShowMapFocusUnit(unitId);
} catch (e) {
console.log('ShowMapFocusUnit failed', e);
}
pas['View.Main'].FViewMain.ShowMapFocusUnit(unitId);
end;
end;
end;
end;
procedure TFViewUnits.btnRefreshClick(Sender: TObject);
begin
GetUnits;
end;
procedure TFViewUnits.GetUnits;
[async] procedure TFViewUnits.GetUnits;
var
xdcResponse: TXDataClientResponse;
respObj: TJSObject;
unitCount: Integer;
begin
if FLoading then Exit;
if FLoading then
Exit;
FLoading := True;
console.log('GetUnits: Invoking API...');
Utils.ShowSpinner('spinner');
if FFirstLoad then
Utils.ShowSpinner('spinner');
try
try
xdcResponse := await(xdwcUnits.RawInvokeAsync('IApiService.GetUnitList', []));
console.log('RawInvoke returned:', xdcResponse.Result);
respObj := TJSObject(xdcResponse.Result);
xdwdsUnits.Close;
console.log('Units dataset closed');
xdwdsUnits.SetJsonData(respObj['data']);
console.log('JsonData set on units dataset:', respObj['data']);
xdwdsUnits.Open;
console.log('Units dataset opened. Record count:', xdwdsUnits.RecordCount);
unitCount := Integer(respObj['count']);
lblEntries.Caption := Format('%d units', [unitCount]);
console.log('Units label updated:', lblEntries.Caption);
except
on E: EXDataClientRequestException do
begin
console.log('XData exception (units):', E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
on E: Exception do
Utils.ShowErrorModal(E.Message);
end;
finally
if FFirstLoad then
Utils.HideSpinner('spinner');
FFirstLoad := False;
FLoading := False;
Utils.HideSpinner('spinner');
console.log('GetUnits complete');
end;
end;
procedure TFViewUnits.tmrRefreshTimer(Sender: TObject);
procedure TFViewUnits.RefreshData;
begin
Console.Log('Units.RefreshData');
GetUnits;
end;
......
......@@ -150,7 +150,6 @@ begin
isActive := false
else
isActive := true;
FViewMain.EditUser('Edit', Username.innerText, FullName.innerText, PhoneNum.innerText, Email.innerText, isAdmin, isActive);
end;
......@@ -483,8 +482,7 @@ end;
procedure TFViewUsers.btnAddUserClick(Sender: TObject);
begin
//Info := '';
FViewMain.EditUser('Add', '', '', '', '', false, true);
//Info := '';mm
end;
procedure TFViewUsers.btnCloseNotificationClick(Sender: TObject);
......
......@@ -4,6 +4,7 @@ span.card {
border: none;
}
/* --- Login Screen Styling --- */
.login-card {
display: inline-block; /* Or use d-flex on the parent to center it */
......@@ -85,3 +86,5 @@ html, body {
}
......@@ -23,7 +23,8 @@ uses
View.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html},
View.ComplaintDetails in 'View.ComplaintDetails.pas' {FViewComplaintDetails: TWebForm} {*.html},
View.UnitDetails in 'View.UnitDetails.pas' {FViewUnitDetails: TWebForm} {*.html},
uMapFilters in 'uMapFilters.pas';
uMapFilters in 'uMapFilters.pas',
View.ComplaintArchive in 'View.ComplaintArchive.pas' {FViewComplaintArchive: TWebForm} {*.html};
{$R *.res}
......
......@@ -97,8 +97,9 @@
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_Release>3</VerInfo_Release>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebOutputPath>..\emiMobileServer\bin\static</TMSWebOutputPath>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
......@@ -188,6 +189,11 @@
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="uMapFilters.pas"/>
<DCCReference Include="View.ComplaintArchive.pas">
<Form>FViewComplaintArchive</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<None Include="index.html"/>
<None Include="css\app.css"/>
<None Include="css\spinner.css"/>
......@@ -506,12 +512,7 @@
<Source>
<Source Name="MainSource">webEmiMobile.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k290.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp290.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k290.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp290.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>
<Excluded_Packages/>
</Delphi.Personality>
<Deployment Version="5">
<DeployFile Condition="'$(SKIADIR)'==''" Required="true" LocalName="$(BDS)\bin64\sk4d.dll" Configuration="Debug" Class="Skia">
......
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