Commit 376416dd by emsys

merge conflicts

parents 8bb49d94 d1a82903
......@@ -21,3 +21,5 @@ emiMobileServer/logs/*
*.tvsconfig
*.dxsettings
*.zip
......@@ -10,7 +10,6 @@ uses
type
TApiDatabaseModule = class(TDataModule)
OracleUniProvider1: TOracleUniProvider;
uqBooking: TUniQuery;
uqMapUnits: TUniQuery;
uqUnitList: TUniQuery;
uqComplaintUnits: TUniQuery;
......@@ -60,12 +59,6 @@ type
uqCFSMemosTIMESTAMP: TDateTimeField;
uqCFSMemosBADGE_NUMBER: TStringField;
uqCFSMemosREMARKS: TStringField;
uqMapUnitsENTRYID: TFloatField;
uqMapUnitsUNITID: TFloatField;
uqMapUnitsUNITNAME: TStringField;
uqMapUnitsUNIT_DISTRICT: TStringField;
uqMapUnitsGPS_LATITUDE: TFloatField;
uqMapUnitsGPS_LONGITUDE: TFloatField;
uqComplaintListcomplaintNumber: TStringField;
uqComplaintListPRIORITY_COLOR: TFloatField;
uqComplaintListDISTRICT_DESC: TStringField;
......@@ -128,6 +121,57 @@ type
uqMapComplaintUnitsListDATECLEARED: TDateTimeField;
uqMapComplaintUnitsListLOCATION: TStringField;
uqMapComplaintsADDRESS: TStringField;
uqMapUnitsENTRYID: TFloatField;
uqMapUnitsUNITID: TFloatField;
uqMapUnitsUNITNAME: TStringField;
uqMapUnitsUNIT_DISTRICT: TStringField;
uqMapUnitsGPS_LATITUDE: TFloatField;
uqMapUnitsGPS_LONGITUDE: TFloatField;
uqMapUnitsCALL_TYPE: TStringField;
uqMapUnitsPRIORITY: TStringField;
uqMapUnitsUNIT_STATUS_DESC: TStringField;
uqComplaintDetailsHISTORY: TFloatField;
uqComplaintDetailsCONTACTS: TFloatField;
uqComplaintDetailsWARNINGS: TFloatField;
uqComplaintDetailsADDRESSID: TFloatField;
uqComplaintDetailsAGENCY: TStringField;
uqComplaintDetailsSTRNUMBER: TFloatField;
uqComplaintDetailsSTRHNUMBER: TStringField;
uqComplaintDetailsSTRPREFIX: TStringField;
uqComplaintDetailsSTRNAME: TStringField;
uqComplaintDetailsSTRSUFFIX: TStringField;
uqComplaintDetailsCITY: TStringField;
uqComplaintHistory: TUniQuery;
uqComplaintContacts: TUniQuery;
uqComplaintWarnings: TUniQuery;
uqMapUnitsUPDATE_TIME: TDateTimeField;
uqMapUnitsOFFICER1_LNAME: TStringField;
uqMapUnitsOFFICER1_FNAME: TStringField;
uqMapUnitsOFFICER1_EMPNUM: TStringField;
uqMapUnitsOFFICER2_LNAME: TStringField;
uqMapUnitsOFFICER2_FNAME: TStringField;
uqMapUnitsOFFICER2_EMPNUM: TStringField;
uqUnitDetails: TUniQuery;
uqUnitDetailsUNITID: TFloatField;
uqUnitDetailsUNITNAME: TStringField;
uqUnitDetailsCARNUMBER_DESC: TStringField;
uqUnitDetailsDISTRICT_DESC: TStringField;
uqUnitDetailsLOCATION: TStringField;
uqUnitDetailsUNIT_STATUS_DESC: TStringField;
uqUnitDetailsOFFICER1_LNAME: TStringField;
uqUnitDetailsOFFICER1_FNAME: TStringField;
uqUnitDetailsOFFICER1_EMPNUM: TStringField;
uqUnitDetailsOFFICER2_LNAME: TStringField;
uqUnitDetailsOFFICER2_FNAME: TStringField;
uqUnitDetailsOFFICER2_EMPNUM: TStringField;
uqUnitDetailsUPDATE_TIME: TDateTimeField;
uqUnitLogs: TUniQuery;
uqUnitLogsLOG_TIME: TDateTimeField;
uqUnitLogsCOMPLAINT_NUM: TStringField;
uqUnitLogsLOG_TEXT: TStringField;
uqMapUnitsDIS_UNITID: TFloatField;
uqMapComplaintsBUSINESS: TStringField;
uqComplaintDetailsBUSINESS: TStringField;
procedure uqComplaintListCalcFields(DataSet: TDataSet);
procedure uqMapComplaintsCalcFields(DataSet: TDataSet);
private
......
......@@ -22,6 +22,14 @@ type
[HttpGet] function GetComplaintMap: TJSONObject;
[HttpGet] function GetUnitMap: TJSONObject;
[HttpGet] function GetComplaintDetails(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;
[HttpGet] function GetComplaintWarnings(const ComplaintId: string): TJSONObject;
[HttpGet] function GetUnitDetails(const UnitId: string): TJSONObject;
[HttpGet] function GetUnitLogs(const UnitId: string): TJSONObject;
end;
implementation
......
[Settings]
LogFileNum=531
LogFileNum=610
webClientVersion=0.1.0
<div class="row">
<div class="col-lg-12">
<h1 class="page-header" id="view.userprofile.title">Admin User Profile</h1>
<div role="form">
<div class="form-group">
<label id="view.userprofile.form.lblUserName">User Name:</label>
<input id="view.userprofile.form.edtUserName" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblFullName">User Fullname:</label>
<input id="view.userprofile.form.edtFullName" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblAgency">User Agency:</label>
<input id="view.userprofile.form.edtAgency" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblBadgeNum">User Bage #:</label>
<input id="view.userprofile.form.edtBadgeNum" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblUserId">User Id:</label>
<input id="view.userprofile.form.edtUserId" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblPersonnelId">Personnel Id:</label>
<input id="view.userprofile.form.edtPersonnelId" class="form-control">
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="view.userprofile.form.chkAdminUser">
<label class="custom-control-label" for="view.userprofile.form.chkAdminUser">Admin User</label>
</div>
<div class="form-input">
<div><label id="lblinfo" class="py-2" style="font-size: 1.00rem;"></label></div>
<div><input class="form-control input-sm" id="edtusername" width='50%'/></div>
<div class="py-2"><input class="form-control input-sm" id="edtpassword" width='50%'/></div>
<button id="btnadduser"></button>
<div><label id="lblresult" class="py-2" style="font-size: 1.00rem;"></label></div>
<div class="container-fluid p-3">
<h1 id="view.userprofile.title" class="h2 border-bottom pb-2 mb-4 text-primary">
Admin User Profile
</h1>
<div class="card shadow-sm mb-4">
<div class="card-body">
<h5 class="card-title text-secondary mb-3">User Details</h5>
<div class="row g-3">
<div class="col-12">
<label id="view.userprofile.form.lblFullName" for="view.userprofile.form.edtFullName" class="form-label fw-bold">User Fullname:</label>
<input id="view.userprofile.form.edtFullName" type="text" class="form-control">
</div>
<div class="col-md-6">
<label id="view.userprofile.form.lblUserName" for="view.userprofile.form.edtUserName" class="form-label fw-bold">User Name:</label>
<input id="view.userprofile.form.edtUserName" type="text" class="form-control">
</div>
<div class="col-md-6">
<label id="view.userprofile.form.lblAgency" for="view.userprofile.form.edtAgency" class="form-label fw-bold">User Agency:</label>
<input id="view.userprofile.form.edtAgency" type="text" class="form-control">
</div>
<div class="col-md-4">
<label id="view.userprofile.form.lblBadgeNum" for="view.userprofile.form.edtBadgeNum" class="form-label small text-muted">User Badge #:</label>
<input id="view.userprofile.form.edtBadgeNum" type="text" class="form-control">
</div>
<div class="col-md-4">
<label id="view.userprofile.form.lblUserId" for="view.userprofile.form.edtUserId" class="form-label small text-muted">User Id:</label>
<input id="view.userprofile.form.edtUserId" type="text" class="form-control">
</div>
<div class="col-md-4">
<label id="view.userprofile.form.lblPersonnelId" for="view.userprofile.form.edtPersonnelId" class="form-label small text-muted">Personnel Id:</label>
<input id="view.userprofile.form.edtPersonnelId" type="text" class="form-control">
</div>
<div class="col-12">
<div class="form-check mt-2">
<input type="checkbox" class="form-check-input" id="view.userprofile.form.chkAdminUser">
<label class="form-check-label user-select-none" for="view.userprofile.form.chkAdminUser">
Admin User
</label>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 bg-light">
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">Quick Actions / Create User</h6>
<div class="mb-2" style="min-height: 1.5rem;">
<label id="lblinfo" class="text-info fw-bold mb-0"></label>
</div>
<div class="row g-2 align-items-end">
<div class="col-md-5">
<label class="form-label small text-muted mb-1">Username</label>
<input id="edtusername" type="text" class="form-control form-control-sm">
</div>
<div class="col-md-5">
<label class="form-label small text-muted mb-1">Password</label>
<input id="edtpassword" type="password" class="form-control form-control-sm">
</div>
<div class="col-md-2">
<button id="btnadduser" class="btn btn-primary btn-sm w-100">
<i class="fa fa-plus me-1"></i> Add
</button>
</div>
</div>
<div class="mt-2">
<label id="lblresult" class="text-success small mb-0"></label>
</div>
</div>
</div>
</div>
<!-- Sticky local navbar (Complaint Details) -->
<div class="sticky-top">
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<div id="cdetails_title" class="navbar-brand mb-0 h5 text-white">Complaint Details</div>
</div>
<div class="col-auto">
<button id="btn_close_complaint_details" type="button" class="btn btn-outline-light w-100 h-100">
<i class="fa fa-times me-1"></i>
<span class="d-none d-sm-inline">Close</span>
</button>
</div>
</div>
</div>
</nav>
</div>
<!-- Complaint details content -->
<div class="container-fluid mt-2 complaint-details-page">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<!-- Outer card -->
<div class="card border-0 shadow-sm">
<div class="card-body p-3 bg-light">
<!-- Summary panel -->
<div class="card mb-3">
<div class="card-body py-2">
<div class="row gy-1">
<div class="col-5 col-sm-4 fw-semibold">Complaint</div>
<div class="col-7 col-sm-8" id="lbl_complaint_number"></div>
<div class="col-5 col-sm-4 fw-semibold">Priority</div>
<div class="col-7 col-sm-8" id="lbl_priority"></div>
<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;">
<div class="col-5 col-sm-4 fw-semibold">Status</div>
<div class="col-7 col-sm-8" id="lbl_status"></div>
<!-- Sticky block: Summary header + (expanded) summary body + buttons -->
<div class="sticky-top bg-light" style="z-index:20;">
<div class="col-5 col-sm-4 fw-semibold">Dispatch Code</div>
<div class="col-7 col-sm-8" id="lbl_dispatch_code"></div>
<!-- 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"
aria-expanded="true"
aria-controls="cdetails_summary">
<span class="fw-semibold text-dark" id="lbl_summary_title">Summary</span>
<div class="col-5 col-sm-4 fw-semibold">Dispatch District</div>
<div class="col-7 col-sm-8" id="lbl_dispatch_district"></div>
<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>
<div class="col-5 col-sm-4 fw-semibold">Address</div>
<div class="col-7 col-sm-8" id="lbl_address"></div>
</div>
</div>
</div>
<!-- Summary body (stays sticky because it's inside the sticky block) -->
<div id="cdetails_summary" class="collapse show">
<div class="card border-0 shadow-sm mb-2">
<div class="card-body py-2">
<!-- Action tabs (green buttons) -->
<div class="mb-3">
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-success btn-sm px-3" id="btn_history">History</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_warnings">Warnings</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_contacts">Contacts</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_cmp">CMP</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_e911">E-911</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_rem">REM</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_unt">UNT</button>
</div>
</div>
<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"></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>
<tr id="row_business" class="d-none">
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Business:</th>
<td class="w-100" id="lbl_business"></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Details table (thead in HTML, tbody filled by TWebDBListControl) -->
<div class="card">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 60vh; overflow-y: auto;">
<table class="table table-sm table-striped mb-0">
<thead class="table-light sticky-top">
<tr>
<th style="width: 70px;">Type</th>
<th style="width: 150px;">Timestamp</th>
<th>Remarks</th>
</tr>
</thead>
<tbody id="tbl_complaint_details"></tbody>
</table>
</div>
</div>
</div>
<!-- Buttons -->
<div class="pb-2">
<div class="d-flex flex-wrap gap-2 justify-content-center">
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_remarks" aria-pressed="true">Remarks</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_history">History</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_warnings">Warnings</button>
<button type="button" class="btn btn-success btn-sm px-3" id="btn_contacts">Contacts</button>
</div>
<!-- Remarks toggle filters (only visible on Remarks tab) -->
<div class="d-flex flex-wrap gap-2 justify-content-center mt-2" id="row_remarks_toggles">
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_cmp" aria-pressed="true">CMP</button>
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_e911" aria-pressed="true">E-911</button>
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_rem" aria-pressed="true">REM</button>
<button type="button" class="btn btn-success btn-sm px-3 active" id="btn_unt" aria-pressed="true">UNT</button>
</div>
</div>
</div>
<!-- Tables (scroll with page) -->
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div id="tbl_remarks"></div>
<div id="tbl_history" class="tab-hidden"></div>
<div id="tbl_warnings" class="d-none d-md-block"></div>
<div id="lst_warnings" class="d-block d-md-none"></div>
<div id="tbl_contacts" class="tab-hidden"></div>
</div>
</div>
</div>
<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
</button>
</div>
......@@ -24,49 +24,6 @@ object FViewComplaints: TFViewComplaints
Visible = False
WidthPercent = 100.000000000000000000
end
object btnGroup: TWebButton
Left = 180
Top = 110
Width = 43
Height = 25
Caption = 'Group'
ChildOrder = 1
ElementClassName = 'btn btn-light'
ElementID = 'complaints_btngroup'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnFilter: TWebButton
Left = 242
Top = 110
Width = 37
Height = 25
Caption = 'Filter'
ChildOrder = 1
ElementClassName = 'btn btn-light'
ElementID = 'complaints_btnfilter'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnRefresh: TWebButton
Left = 114
Top = 110
Width = 53
Height = 25
Caption = 'Refresh'
ChildOrder = 1
ElementClassName = 'btn btn-light'
ElementID = 'complaints_btnrefresh'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnRefreshClick
end
object dblComplaintsList: TWebDBListControl
Left = 36
Top = 148
......@@ -86,18 +43,21 @@ object FViewComplaints: TFViewComplaints
DataSource = wdsComplaints
ItemTemplate =
'<div class="list-section-header small fw-semibold bg-secondary t' +
'ext-white rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><div cl' +
'ass="card border shadow-sm" style="--bs-card-bg:(%PriorityCo' +
'lor%);--bs-card-color:(%PriorityTextColor%);"> <div class="card' +
'-body py-2 px-3"> <div class="fw-bold text-uppercase small"> ' +
' (%Priority%): (%DispatchCodeDesc%) </div> <div class=' +
'"small">(%Address%)</div> <div class="small text-opacity-75 d' +
'-flex align-items-center"> <span> (%Complaint%): (%S' +
'tatus%)&nbsp;&nbsp;(%DistrictSector%) </span> <button ' +
'type="button" class="btn btn-primary btn-sm ms-auto' +
' complaint-details-btn" data-id="(%ComplaintId%)"> ' +
' Details </button> </div> <div class="small tex' +
't-opacity-75">(%DateReported%)</div> </div></div>'
'ext-white rounded-1 px-2 mb-1">(%DistrictHeader%)</div><div clas' +
's="card border shadow-sm" style="--bs-card-bg:(%PriorityColor%);' +
'--bs-card-color:(%PriorityTextColor%);"><div class="card-body py' +
'-2 px-3 d-flex gap-2"><div class="flex-grow-1"><div class="fw-bo' +
'ld text-uppercase small">(%Priority%): (%DispatchCodeDesc%)</div' +
'><div class="small">(%Address%)</div><div class="small d-none co' +
'mplaint-business" data-business="(%Business%)">(%Business%)</div' +
'><div class="small text-opacity-75">(%Complaint%): (%Status%)&nb' +
'sp;&nbsp;(%DistrictSector%)</div><div class="small text-opacity-' +
'75">(%DateReported%)</div></div><div class="d-flex flex-column j' +
'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>'
ListSource = wdsComplaints
end
object xdwcComplaints: TXDataWebClient
......@@ -127,6 +87,9 @@ object FViewComplaints: TFViewComplaints
object xdwdsComplaintsAddress: TStringField
FieldName = 'Address'
end
object xdwdsComplaintsBusiness: TStringField
FieldName = 'Business'
end
object xdwdsComplaintsStatus: TStringField
FieldName = 'Status'
end
......
<div class="d-flex flex-column h-100">
<div class="sticky-top">
<!-- Local navbar (Complaints) -->
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<span id="complaints_title" class="navbar-brand mb-0 h5 text-white">Complaints</span>
</div>
<div class="col">
<button id="complaints_btnrefresh" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
</button>
</div>
<div class="col">
<button id="complaints_btngroup" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
</button>
</div>
<div class="col">
<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>
</nav>
<!-- Header / controls (non-scrolling) -->
<div class="flex-shrink-0">
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2">
<div class="container-fluid">
<div class="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<input id="complaints_search" class="form-control" placeholder="Search...">
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2">
<div class="container-fluid">
<div class="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<input id="complaints_search" class="form-control" placeholder="Search...">
</div>
</div>
</div>
</div>
</div>
<!-- Complaints list container -->
<div class="container-fluid mt-2">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<!-- This is where the DBListControl will inject cards -->
<div id="complaints_dbl_complaint_list" class="d-flex flex-column gap-2">
<!-- Cards will render here -->
<!-- Scrolling list area -->
<div class="flex-grow-1 overflow-auto" style="min-height:0;">
<div class="container-fluid mt-2">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<!-- This is where the DBListControl will inject cards -->
<div id="complaints_dbl_complaint_list" class="d-flex flex-column gap-2">
<!-- Cards will render here -->
</div>
<label id="complaints_lblentries" class="mt-2 d-block"></label>
</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>
......@@ -24,9 +24,6 @@ type
xdwdsComplaintsDispatchDistrict: TStringField;
xdwdsComplaintsDateReported: TStringField;
lblEntries: TWebLabel;
btnRefresh: TWebButton;
btnGroup: TWebButton;
btnFilter: TWebButton;
wdsComplaints: TWebDataSource;
xdwdsComplaintsComplaintId: TStringField;
xdwdsComplaintsDistrictHeader: TStringField;
......@@ -35,6 +32,7 @@ type
xdwdsComplaintsPriorityTextColor: TStringField;
xdwdsComplaintsDistrictSector: TStringField;
tmrRefresh: TWebTimer;
xdwdsComplaintsBusiness: TStringField;
procedure WebFormCreate(Sender: TObject);
procedure btnRefreshClick(Sender: TObject);
procedure tmrRefreshTimer(Sender: TObject);
......@@ -44,6 +42,7 @@ type
FLoading: Boolean;
[async] procedure GetComplaints;
procedure HandleListClick(e: TJSMouseEvent);
procedure ShowHideBusinessRows;
public
property OnShowDetails: TSelectProc read FSelectProc write FSelectProc;
end;
......@@ -62,94 +61,135 @@ begin
tmrRefresh.Enabled := False;
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);
}
};
}
end;
end;
procedure TFViewComplaints.ShowHideBusinessRows;
var
nodes: TJSNodeList;
i: Integer;
el: TJSHTMLElement;
businessText: string;
begin
nodes := document.querySelectorAll('.complaint-business');
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
businessText := string(el.getAttribute('data-business'));
if Trim(businessText) <> '' then
el.classList.remove('d-none')
else
el.classList.add('d-none');
end;
end;
procedure TFViewComplaints.HandleListClick(e: TJSMouseEvent);
var el: TJSElement; id: string;
var
el: TJSElement;
btn: TJSElement;
complaintId: string;
begin
btn := nil;
el := TJSElement(e.target);
if (el is TJSHtmlElement) and TJSHtmlElement(el).classList.contains('complaint-details-btn') then
asm
btn = (el && el.closest) ? el.closest('.complaint-details-btn') : null;
end;
if (btn <> nil) and (btn is TJSHtmlElement) then
begin
id := string(TJSHtmlElement(el).dataset['id']); // comes from (%ComplaintId%)
complaintId := string(TJSHtmlElement(btn).getAttribute('data-id'));
e.preventDefault;
e.stopPropagation;
if Assigned(FSelectProc) then
FSelectProc(id);
asm
if (window.showComplaintDetails) window.showComplaintDetails(complaintId);
end;
end
else
begin
btn := nil;
asm
btn = (el && el.closest) ? el.closest('.btn-complaint-map') : null;
end;
if (btn <> nil) and (btn is TJSHtmlElement) then
begin
complaintId := string(TJSHtmlElement(btn).getAttribute('data-id'));
e.preventDefault;
e.stopPropagation;
asm
pas['View.Main'].FViewMain.ShowMapFocusComplaint(complaintId);
end;
end;
end;
end;
procedure TFViewComplaints.WebFormDestroy(Sender: TObject);
begin
Document.removeEventListener('click', @HandleListClick);
end;
//HTML for individual complaint cards can be found in the twebdblistcontrol HTMLString property
//Note: HTML for individual complaint cards can be found in the twebdblistcontrol HTMLString property
procedure TFViewComplaints.btnRefreshClick(Sender: TObject);
begin
GetComplaints;
end;
procedure TFViewComplaints.GetComplaints;
[async] procedure TFViewComplaints.GetComplaints;
var
xdcResponse: TXDataClientResponse;
respObj: TJSObject;
complaintsCount: Integer;
begin
if FLoading then Exit;
FLoading := True;
console.log('GetComplaints: Invoking API...');
if FLoading then
Exit;
FLoading := True;
ShowSpinner('spinner');
try
try
xdcResponse := await(xdwcComplaints.RawInvokeAsync('IApiService.GetComplaintList', []));
console.log('RawInvoke returned:', xdcResponse.Result);
respObj := TJSObject(xdcResponse.Result);
xdwdsComplaints.Close;
console.log('Dataset closed');
xdwdsComplaints.SetJsonData(respObj['data']);
console.log('JsonData set on dataset:', respObj['data']);
xdwdsComplaints.Open;
console.log('PriorityColor field name = ' +
xdwdsComplaintsPriorityColor.FieldName +
' sample value = ' +
xdwdsComplaintsPriorityColor.AsString);
if xdwdsComplaints.RecordCount > 0 then
begin
console.log('First record - Complaint:' + xdwdsComplaints.FieldByName('Complaint').AsString);
end;
ShowHideBusinessRows;
complaintsCount := Integer(respObj['count']);
lblEntries.Caption := Format('%d active complaints', [complaintsCount]);
console.log('Label updated:' + lblEntries.Caption);
except
on E: EXDataClientRequestException do
begin
console.log('XData exception:' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
on E: Exception do
Utils.ShowErrorModal(E.Message);
end;
finally
console.log('GetComplaints complete');
HideSpinner('spinner');
FLoading := False;
end;
HideSpinner('spinner');
end;
procedure TFViewComplaints.tmrRefreshTimer(Sender: TObject);
begin
GetComplaints;
console.log('tmrRefreshTimer fired');
end;
end.
......
......@@ -77,6 +77,17 @@ object FViewMain: TFViewMain
OnClick = lblUsersClick
Caption = 'Users'
end
object lblMainTitle: TWebLabel
Left = 131
Top = 31
Width = 3
Height = 15
ElementID = 'lbl_main_title'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebPanel1: TWebPanel
Left = 136
Top = 110
......
<div class="d-flex flex-column vh-100">
<!-- Top Nav -->
<nav class="navbar navbar-light bg-primary border-light text-light fixed-top">
<nav class="navbar navbar-light bg-primary border-light text-light py-2 flex-shrink-0">
<div class="container-fluid">
<!-- Left: Font button -->
<button id="view.main.btnfont" type="button" class="btn btn-outline-primary text-light border-light btn-sm">
font
</button>
<!-- Center: App title -->
<a id="view.main.apptitle" class="navbar-brand text-light ms-3" href="index.html">emiMobile</a>
<!-- App title + current view title -->
<div class="d-flex align-items-center ms-3">
<a id="view.main.apptitle" class="navbar-brand fw-bold text-light mb-0 me-1" href="index.html">emiMobile</a>
<span class="navbar-brand text-light mb-0 mx-0">-</span>
<span id="lbl_main_title" class="navbar-brand text-light mb-0 ms-1"></span>
</div>
<!-- Right: Connection label -->
<span id="view.main.lblconnection" class="navbar-text text-light ms-auto">Connected</span>
</div>
<span id="view.main.lblconnection" class="navbar-text text-light ms-auto"></span>
</div>
</nav>
<!-- Main content: fills space between navbars -->
<main id="main.webpanel" class="flex-grow-1 overflow-auto mt-5 mb-5">
<main id="main.webpanel" class="flex-grow-1 position-relative p-0 overflow-hidden" style="min-height:0;">
<!-- TWebPanel content gets injected here -->
</main>
<!-- Bottom Nav -->
<nav class="navbar navbar-dark bg-primary fixed-bottom py-2">
<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">
......@@ -46,10 +46,10 @@
<!-- Spinner -->
<div id="spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
<div></div><div></div><div></div><div></div>
<div></div><div></div><div></div><div></div>
</div>
<div class="lds-roller">
<div></div><div></div><div></div><div></div>
<div></div><div></div><div></div><div></div>
</div>
</div>
<!-- Error modal -->
......
......@@ -24,6 +24,7 @@ type
btnComplaints: TWebButton;
btnUnits: TWebButton;
tmrBadgeCounts: TWebTimer;
lblMainTitle: TWebLabel;
procedure WebFormCreate(Sender: TObject);
procedure mnuLogoutClick(Sender: TObject);
procedure wllblUserProfileClick(Sender: TObject);
......@@ -44,8 +45,9 @@ type
procedure ShowCrudForm( AFormClass: TWebFormClass );
//procedure EditUser( AParam, BParam, CParam, DParam, EParam: string);
function GetUserInfo: string;
procedure SetActiveNavButton(const BtnId: string);
[async] procedure RefreshBadgesAsync;
procedure ShowUnitDetails(UnitId: string);
procedure SetHeaderTitle(const title: string);
public
{ Public declarations }
class procedure Display(LogoutProc: TLogoutProc);
......@@ -53,6 +55,9 @@ type
procedure EditUser( Mode, FullName, Username, Phone, Email: string; admin, active: boolean);
procedure ShowUserForm(Info: string);
procedure ShowComplaintDetails(ComplaintId: string);
procedure SetActiveNavButton(const BtnId: string);
procedure ShowMapFocusUnit(const unitId: string);
procedure ShowMapFocusComplaint(const complaintId: string);
end;
var
......@@ -71,6 +76,7 @@ uses
View.Admin,
View.Users,
View.EditUser,
View.UnitDetails,
Utils;
{$R *.dfm}
......@@ -87,9 +93,20 @@ begin
lblUsers.Visible := false;
Utils.HideSpinner('spinner');
ShowForm(TFViewMap);
SetHeaderTitle('Map');
RefreshBadgesAsync;
end;
procedure TFViewMain.SetHeaderTitle(const title: string);
var el: TJSElement;
begin
el := Document.getElementById('lbl_main_title');
if el <> nil then
el.innerHTML := title
else
console.log('SetHeaderTitle: lbl_main_title not found');
end;
procedure TFViewMain.lblUsersClick(Sender: TObject);
begin
......@@ -136,6 +153,7 @@ end;
procedure TFViewMain.btnComplaintsClick(Sender: TObject);
begin
SetHeaderTitle('Complaints');
SetActiveNavButton('view.main.btncomplaints');
ShowForm(TFViewComplaints);
......@@ -149,12 +167,14 @@ end;
procedure TFViewMain.btnMapClick(Sender: TObject);
begin
SetHeaderTitle('Map');
SetActiveNavButton('view.main.btnmap');
ShowForm(TFViewMap);
end;
procedure TFViewMain.btnUnitsClick(Sender: TObject);
begin
SetHeaderTitle('Units');
SetActiveNavButton('view.main.btnunits');
ShowForm(TFViewUnits);
end;
......@@ -197,11 +217,19 @@ end;
procedure TFViewMain.ShowComplaintDetails(ComplaintId: string);
begin
SetHeaderTitle('Complaint Details');
if Assigned(FChildForm) then
FChildForm.Free;
FChildForm := TFViewComplaintDetails.CreateForm(WebPanel1.ElementID, ComplaintId);
end;
procedure TFViewMain.ShowUnitDetails(UnitId: string);
begin
SetHeaderTitle('Unit Details');
if Assigned(FChildForm) then
FChildForm.Free;
FChildForm := TFViewUnitDetails.CreateForm(WebPanel1.ElementID, UnitId);
end;
procedure TFViewMain.SetActiveNavButton(const btnId: string);
......@@ -264,4 +292,41 @@ begin
end;
procedure TFViewMain.ShowMapFocusUnit(const unitId: string);
var
pendingUnitId: string;
begin
SetHeaderTitle('Map');
SetActiveNavButton('view.main.btnmap');
ShowForm(TFViewMap);
pendingUnitId := unitId;
window.setTimeout(
procedure
begin
if (FChildForm <> nil) and (FChildForm is TFViewMap) then
TFViewMap(FChildForm).FocusUnit(pendingUnitId);
end, 50);
end;
procedure TFViewMain.ShowMapFocusComplaint(const complaintId: string);
var
pendingComplaintId: string;
begin
SetHeaderTitle('Map');
SetActiveNavButton('view.main.btnmap');
ShowForm(TFViewMap);
pendingComplaintId := complaintId;
window.setTimeout(
procedure
begin
if (FChildForm <> nil) and (FChildForm is TFViewMap) then
TFViewMap(FChildForm).FocusComplaint(pendingComplaintId);
end, 50);
end;
end.
......@@ -2,81 +2,15 @@ object FViewMap: TFViewMap
Width = 475
Height = 802
ElementFont = efCSS
object btnMenu: TWebButton
Left = 62
Top = 66
Width = 41
Height = 25
Caption = 'Menu'
ChildOrder = 1
ElementID = 'map.btnmenu'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnAlerts: TWebButton
Left = 148
Top = 66
Width = 35
Height = 25
Caption = 'Alerts'
ChildOrder = 1
ElementID = 'map.btnalerts'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnGroups: TWebButton
Left = 194
Top = 66
Width = 41
Height = 25
Caption = 'Groups'
ChildOrder = 1
ElementID = 'map.btngroups'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnLocate: TWebButton
Left = 246
Top = 66
Width = 39
Height = 25
Caption = 'Locate'
ChildOrder = 1
ElementID = 'map.btnlocate'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnFilters: TWebButton
Left = 297
Top = 66
Width = 35
Height = 25
Caption = 'Filters'
ChildOrder = 1
ElementID = 'map.btnfilters'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnDisplay: TWebButton
Left = 351
Top = 66
Width = 40
Height = 25
Caption = 'Display'
ChildOrder = 1
ElementID = 'map.btndisplay'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object pnlMap: TWebPanel
Left = 62
Top = 120
Width = 335
Height = 555
ElementID = 'map_pnlmap'
Caption = 'pnlMap'
ChildOrder = 7
TabOrder = 6
ElementPosition = epIgnore
TabOrder = 0
object lfMap: TTMSFNCLeaflet
Left = 0
Top = 0
......@@ -114,6 +48,18 @@ object FViewMap: TFViewMap
HeadLinks = <>
end
end
object btnFindLocation: TWebButton
Left = 90
Top = 74
Width = 61
Height = 25
Caption = 'Location'
ChildOrder = 1
ElementID = 'btn_find_location'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnFindLocationClick
end
object httpReqGeoJson: TWebHttpRequest
ResponseType = rtText
URL = 'assets/bpddistricts-updated.geojson'
......@@ -128,7 +74,15 @@ object FViewMap: TFViewMap
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
end
end
<!-- Root wrapper inside main.webpanel -->
<div id="map.root" class="d-flex flex-column" style="height:100%;">
<div id="map.root" class="d-flex flex-column h-100 w-100">
<!-- Local navbar -->
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<button id="map.btnmenu" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-bars me-1"></i><span class="d-none d-sm-inline">Menu</span>
</button>
</div>
<div class="col">
<button id="map.btnalerts" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-exclamation-circle me-1"></i><span class="d-none d-sm-inline">Alerts</span>
</button>
</div>
<div class="col">
<button id="map.btngroups" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-users me-1"></i><span class="d-none d-sm-inline">Groups</span>
</button>
</div>
<div class="col">
<button id="map.btnlocate" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-location-arrow me-1"></i><span class="d-none d-sm-inline">Locate</span>
</button>
<!-- New: offcavnvas is a bootstrap class that adds an easy slide in modal -->
<div class="offcanvas offcanvas-end"
tabindex="-1"
id="map_filters_offcanvas"
aria-labelledby="map_filters_offcanvas_label"
style="--bs-offcanvas-width: 280px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="map_filters_offcanvas_label">Map Filters</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="map_filter_units" checked>
<label class="form-check-label" for="map_filter_units">Show Units</label>
</div>
<div class="col">
<button id="map.btnfilters" 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 class="form-check">
<input class="form-check-input" type="checkbox" id="map_filter_complaints" checked>
<label class="form-check-label" for="map_filter_complaints">Show Complaints</label>
</div>
<div class="col">
<button id="map.btndisplay" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sun me-1"></i><span class="d-none d-sm-inline">Display</span>
</button>
<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>
</div>
</div>
</div>
</nav>
<div class="d-grid gap-2 mt-4">
<button type="button" class="btn btn-primary" id="map_filters_apply" data-bs-dismiss="offcanvas">
Apply
</button>
<button type="button" class="btn btn-outline-secondary" id="map_filters_reset">
Reset
</button>
</div>
<!-- Map fills remaining space -->
<div class="flex-grow-1" style="min-height:400px;">
<!-- TWebPanel (pnlMap) will render itself here -->
<div id="map_pnlmap" class="w-100 h-100"></div>
<div class="mt-3 small text-muted">
<span class="fw-semibold">Active:</span>
<span id="map_filters_summary"></span>
</div>
</div>
</div>
<div class="flex-grow-1 position-relative" style="min-height:0;">
<div id="map_pnlmap" class="position-absolute w-100 h-100 top-0 start-0"></div>
</div>
<!-- Locate (bottom-right) -->
<button id="btn_find_location"
type="button"
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) -->
<button id="btn_map_filters"
type="button"
class="btn btn-primary position-absolute top-0 end-0 m-2 shadow"
style="z-index:1000;"
data-bs-toggle="offcanvas"
data-bs-target="#map_filters_offcanvas"
aria-controls="map_filters_offcanvas">
<i class="fa fa-sliders-h"></i>
<span class="d-none d-sm-inline">Filters</span>
</button>
</div>
</div>
object FViewUnitDetails: TFViewUnitDetails
Width = 640
Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS
object tblLogs: TWebDBTableControl
Left = 166
Top = 116
Width = 300
Height = 200
ElementId = 'tbl_logs'
BorderColor = clSilver
ColHeader = False
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
DataField = 'LogTime'
Title = 'Log Time'
TitleElementClassName = 'w-25'
end
item
ElementClassName = 'text-nowrap'
DataField = 'Complaint'
Title = 'Complaint'
end
item
DataField = 'Log'
Title = 'Log'
end>
DataSource = wdsUnitLogs
end
object btnUnitViewOnMap: TWebButton
Left = 370
Top = 326
Width = 96
Height = 25
Caption = 'Map'
ChildOrder = 1
ElementID = 'btn_unit_view_on_map'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnUnitViewOnMapClick
end
object xdwcUnitDetails: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 150
Top = 394
end
object xdwdsUnitLogs: TXDataWebDataSet
Left = 278
Top = 394
object xdwdsUnitLogsLogTime: TStringField
FieldName = 'LogTime'
end
object xdwdsUnitLogsComplaint: TStringField
FieldName = 'Complaint'
end
object xdwdsUnitLogsLog: TStringField
FieldName = 'Log'
end
end
object wdsUnitLogs: TWebDataSource
DataSet = xdwdsUnitLogs
Left = 208
Top = 362
end
object xdwdsUnitDetails: TXDataWebDataSet
Left = 364
Top = 368
object xdwdsUnitDetailsUnitId: TStringField
FieldName = 'UnitId'
end
object xdwdsUnitDetailsUnitName: TStringField
FieldName = 'UnitName'
end
object xdwdsUnitDetailsCarNumberDesc: TStringField
FieldName = 'CarNumberDesc'
end
object xdwdsUnitDetailsDistrict: TStringField
FieldName = 'District'
end
object xdwdsUnitDetailsLocation: TStringField
FieldName = 'Location'
end
object xdwdsUnitDetailsStatus: TStringField
FieldName = 'Status'
end
object xdwdsUnitDetailsOfficer1LName: TStringField
FieldName = 'Officer1Lname'
end
object xdwdsUnitDetailsOfficer1Fname: TStringField
FieldName = 'Officer1Fname'
end
object xdwdsUnitDetailsOfficer1Empnum: TStringField
FieldName = 'Officer1Empnum'
end
object xdwdsUnitDetailsOfficer2Lname: TStringField
FieldName = 'Officer2Lname'
end
object xdwdsUnitDetailsOfficer2Fname: TStringField
FieldName = 'Officer2Fname'
end
object xdwdsUnitDetailsOfficer2Empnum: TStringField
FieldName = 'Officer2Empnum'
end
object xdwdsUnitDetailsUpdateTime: TStringField
FieldName = 'UpdateTime'
end
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 + (expanded) 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="#udetails_summary"
aria-expanded="true"
aria-controls="udetails_summary">
<span class="fw-semibold text-dark" id="lbl_summary_title">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="udetails_summary" 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">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>
</div>
</div>
<!-- Logs header row (stays sticky) -->
<div class="pb-2">
<div class="d-flex align-items-center justify-content-between">
<div class="fw-semibold text-muted">Logs</div>
<!-- Spinner shown while loading logs -->
<div class="spinner-border spinner-border-sm text-secondary d-none" role="status" id="spinner_logs">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
<!-- Logs table (scroll with page) -->
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div id="tbl_logs"></div>
</div>
</div>
</div>
<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;">
Map
</button>
</div>
unit View.UnitDetails;
interface
uses
System.SysUtils, System.Classes, JS, Web,
WEBLib.Forms, WEBLib.Dialogs, WEBLib.Graphics,
XData.Web.Client, XData.Web.JsonDataset, XData.Web.Dataset,
ConnectionModule,
Utils,
Data.DB, WEBLib.DB,
Vcl.Controls, WEBLib.Controls, WEBLib.Grids, WEBLib.DBCtrls, Vcl.StdCtrls,
WEBLib.StdCtrls;
type
TFViewUnitDetails = class(TWebForm)
xdwcUnitDetails: TXDataWebClient;
// Details (single-object) dataset
xdwdsUnitDetails: TXDataWebDataSet;
xdwdsUnitDetailsUnitId: TStringField;
xdwdsUnitDetailsUnitName: TStringField;
xdwdsUnitDetailsCarNumberDesc: TStringField;
xdwdsUnitDetailsDistrict: TStringField;
xdwdsUnitDetailsLocation: TStringField;
xdwdsUnitDetailsStatus: TStringField;
xdwdsUnitDetailsOfficer1Lname: TStringField;
xdwdsUnitDetailsOfficer1Fname: TStringField;
xdwdsUnitDetailsOfficer1Empnum: TStringField;
xdwdsUnitDetailsOfficer2Lname: TStringField;
xdwdsUnitDetailsOfficer2Fname: TStringField;
xdwdsUnitDetailsOfficer2Empnum: TStringField;
xdwdsUnitDetailsUpdateTime: TStringField;
// Logs (array) dataset + table
xdwdsUnitLogs: TXDataWebDataSet;
wdsUnitLogs: TWebDataSource;
tblLogs: TWebDBTableControl;
xdwdsUnitLogsComplaint: TStringField;
xdwdsUnitLogsLog: TStringField;
xdwdsUnitLogsLogTime: TStringField;
btnUnitViewOnMap: TWebButton;
procedure btnUnitViewOnMapClick(Sender: TObject);
private
FUnitId: string;
FLoading: Boolean;
procedure InitializeForm;
procedure SetTextById(const elementId, value: string);
procedure SetHiddenById(const elementId: string; hidden: Boolean);
function FormatOfficer(const lname, fname, empnum: string): string;
[async] procedure LoadUnitAsync;
[async] procedure LoadLogsAsync;
public
class function CreateForm(AElementID, UnitId: string): TWebForm;
end;
var
FViewUnitDetails: TFViewUnitDetails;
implementation
uses
View.Main,
View.Units;
{$R *.dfm}
procedure TFViewUnitDetails.btnUnitViewOnMapClick(Sender: TObject);
begin
if Assigned(FViewMain) then
FViewMain.ShowMapFocusUnit(FUnitId);
console.log('btnViewOnMapClick fired, FUnitId=' + FUnitId);
end;
class function TFViewUnitDetails.CreateForm(AElementID, UnitId: string): TWebForm;
procedure AfterCreate(AForm: TObject);
begin
with TFViewUnitDetails(AForm) do
begin
FUnitId := UnitId;
InitializeForm;
end;
end;
begin
Application.CreateForm(TFViewUnitDetails, AElementID, Result, @AfterCreate);
end;
procedure TFViewUnitDetails.InitializeForm;
begin
xdwcUnitDetails.Connection := DMConnection.ApiConnection;
xdwdsUnitDetails.Connection := DMConnection.ApiConnection;
xdwdsUnitLogs.Connection := DMConnection.ApiConnection;
FLoading := False;
LoadUnitAsync;
end;
procedure TFViewUnitDetails.SetTextById(const elementId, value: string);
var
el: TJSElement;
begin
el := Document.getElementById(elementId);
if el <> nil then
TJSHtmlElement(el).innerText := value;
end;
procedure TFViewUnitDetails.SetHiddenById(const elementId: string; hidden: Boolean);
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(Document.getElementById(elementId));
if el = nil then
Exit;
if hidden then
el.classList.add('d-none')
else
el.classList.remove('d-none');
end;
function TFViewUnitDetails.FormatOfficer(const lname, fname, empnum: string): string;
var
nameText: string;
begin
nameText := Trim(lname);
if nameText = '' then
Exit('');
if Trim(fname) <> '' then
nameText := nameText + ', ' + Trim(fname);
if Trim(empnum) <> '' then
nameText := nameText + ' (' + Trim(empnum) + ')';
Result := nameText;
end;
[async] procedure TFViewUnitDetails.LoadUnitAsync;
var
resp: TXDataClientResponse;
rootObj: TJSObject;
dataObj: TJSObject;
summaryTitle: string;
officer1Text: string;
officer2Text: string;
begin
if FLoading then
Exit;
FLoading := True;
Utils.ShowSpinner('spinner');
try
resp := await(xdwcUnitDetails.RawInvokeAsync('IApiService.GetUnitDetails', [FUnitId]));
rootObj := TJSObject(resp.Result);
dataObj := TJSObject(rootObj['data']);
xdwdsUnitDetails.Close;
xdwdsUnitDetails.SetJsonData(dataObj);
xdwdsUnitDetails.Open;
summaryTitle := xdwdsUnitDetailsUnitName.AsString;
if Trim(summaryTitle) <> '' then
SetTextById('lbl_summary_title', 'Summary for Unit ' + summaryTitle)
else
SetTextById('lbl_summary_title', 'Summary for Unit ' + xdwdsUnitDetailsUnitId.AsString);
SetTextById('lbl_car', xdwdsUnitDetailsCarNumberDesc.AsString);
SetTextById('lbl_unit_name', xdwdsUnitDetailsUnitName.AsString);
SetTextById('lbl_district', xdwdsUnitDetailsDistrict.AsString);
SetTextById('lbl_location', xdwdsUnitDetailsLocation.AsString);
SetTextById('lbl_status', xdwdsUnitDetailsStatus.AsString);
SetTextById('lbl_updated', xdwdsUnitDetailsUpdateTime.AsString);
officer1Text := FormatOfficer(
xdwdsUnitDetailsOfficer1Lname.AsString,
xdwdsUnitDetailsOfficer1Fname.AsString,
xdwdsUnitDetailsOfficer1Empnum.AsString
);
officer2Text := FormatOfficer(
xdwdsUnitDetailsOfficer2Lname.AsString,
xdwdsUnitDetailsOfficer2Fname.AsString,
xdwdsUnitDetailsOfficer2Empnum.AsString
);
SetTextById('lbl_officer1', officer1Text);
SetTextById('lbl_officer2', officer2Text);
await(LoadLogsAsync);
Utils.HideSpinner('spinner');
FLoading := False;
except
on E: EXDataClientRequestException do
begin
Utils.HideSpinner('spinner');
FLoading := False;
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
on E: Exception do
begin
Utils.HideSpinner('spinner');
FLoading := False;
Utils.ShowErrorModal(E.Message);
end;
end;
end;
[async] procedure TFViewUnitDetails.LoadLogsAsync;
var
resp: TXDataClientResponse;
rootObj: TJSObject;
dataArr: TJSArray;
begin
SetHiddenById('spinner_logs', False);
try
resp := await(xdwcUnitDetails.RawInvokeAsync('IApiService.GetUnitLogs', [FUnitId]));
rootObj := TJSObject(resp.Result);
dataArr := TJSArray(rootObj['data']);
console.log('LoadLogsAsync Units rootObj: ' + rootObj.tostring);
xdwdsUnitLogs.Close;
xdwdsUnitLogs.SetJsonData(dataArr);
xdwdsUnitLogs.Open;
SetHiddenById('spinner_logs', True);
except
on E: EXDataClientRequestException do
begin
SetHiddenById('spinner_logs', True);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
on E: Exception do
begin
SetHiddenById('spinner_logs', True);
Utils.ShowErrorModal(E.Message);
end;
end;
end;
end.
......@@ -40,66 +40,22 @@ object FViewUnits: TFViewUnits
DataSource = wdsUnits
ItemTemplate =
'<div class="list-section-header small fw-semibold bg-body-second' +
'ary text-dark rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><di' +
'v class="card border shadow-sm position-relative"> <div class="' +
'card-body py-2 px-3"> <!-- Unit + Status --> <div class="f' +
'w-bold text-uppercase small"> (%UnitName%)&nbsp;-&nbsp;(%St' +
'atus%) </div> <!-- Location --> <div class="small text-' +
'body-secondary mb-1"> (%Location%) </div> <!-- Call t' +
'ype --> <div class="small">(%CallType%)</div> <!-- Divider' +
' line --> <hr class="unit-divider my-1" style="width:80px;mar' +
'gin-left:0;"> <!-- Officers --> <div class="small officer1' +
'">(%Officer1%)</div> <div class="small officer2">(%Officer2%)' +
'</div> </div> <!-- Vertically centered Details button on the r' +
'ight --> <div class="position-absolute top-50 end-0 translate-m' +
'iddle-y pe-2"> <button type="button" class="btn bt' +
'n-primary btn-sm btn-unit-details" data-unitid="(%Uni' +
'tId%)"> Details </button> </div></div>'
'ary text-dark rounded-1 px-2 mb-1">(%DistrictHeader%)</div><div ' +
'class="card border shadow-sm"> <div class="card-body py-2 px-3 ' +
'd-flex gap-2"> <div class="flex-grow-1"> <div class="fw-' +
'bold text-uppercase small">(%UnitName%)&nbsp;-&nbsp;(%Status%)</' +
'div> <div class="small text-body-secondary mb-1">(%Location' +
'%)</div> <div class="small">(%CallType%)</div> <hr cla' +
'ss="unit-divider my-1" style="width: 80px; margin-left: 0" /> ' +
' <div class="small officer1">(%Officer1%)</div> <div clas' +
's="small officer2">(%Officer2%)</div> </div> <div class="d' +
'-flex flex-column justify-content-center gap-1"> <button ty' +
'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>'
ListSource = wdsUnits
end
object btnRefresh: TWebButton
Left = 110
Top = 82
Width = 53
Height = 25
Caption = 'Refresh'
ChildOrder = 1
ElementClassName = 'btn btn-light'
ElementID = 'units_btnrefresh'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnRefreshClick
end
object btnGroup: TWebButton
Left = 186
Top = 82
Width = 43
Height = 25
Caption = 'Group'
ChildOrder = 1
ElementClassName = 'btn btn-light'
ElementID = 'units_btngroup'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnFilter: TWebButton
Left = 250
Top = 82
Width = 37
Height = 25
Caption = 'Filter'
ChildOrder = 1
ElementClassName = 'btn btn-light'
ElementID = 'units_btnfilter'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object wdsUnits: TWebDataSource
AutoEdit = False
DataSet = xdwdsUnits
......
<div class="d-flex flex-column h-100">
<div class="sticky-top">
<!-- Local navbar (Units) -->
<nav class="navbar navbar-dark bg-primary py-2"><!-- removed sticky-top -->
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<span id="units_title" class="navbar-brand mb-0 h5 text-white">Units</span>
</div>
<div class="col">
<button id="units_btnrefresh" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
</button>
</div>
<div class="col">
<button id="units_btngroup" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
</button>
</div>
<div class="col">
<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>
</nav>
<!-- Header / controls (non-scrolling) -->
<div class="flex-shrink-0">
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2"><!-- removed sticky-top -->
<div class="container-fluid">
<div class="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<input id="units_search" class="form-control" placeholder="Search...">
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2">
<div class="container-fluid">
<div class="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<input id="units_search" class="form-control" placeholder="Search...">
</div>
</div>
</div>
</div>
</div> <!-- /sticky-top wrapper -->
<!-- Units list container -->
<div class="container-fluid mt-2">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<!-- This is where the DBListControl will inject cards -->
<div id="units_dbl_unit_list" class="d-flex flex-column gap-2">
<!-- Cards will render here -->
<!-- Scrolling list area -->
<div class="flex-grow-1 overflow-auto" style="min-height:0;">
<div class="container-fluid mt-2">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<!-- This is where the DBListControl will inject cards -->
<div id="units_dbl_unit_list" class="d-flex flex-column gap-2">
<!-- Cards will render here -->
</div>
<!-- Entry Count Label -->
<label id="unitss_lblentries" class="mt-2 d-block"></label>
</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>
......@@ -13,9 +13,6 @@ uses
type
TFViewUnits = class(TWebForm)
dblUnitsList: TWebDBListControl;
btnRefresh: TWebButton;
btnGroup: TWebButton;
btnFilter: TWebButton;
wdsUnits: TWebDataSource;
xdwdsUnits: TXDataWebDataSet;
xdwcUnits: TXDataWebClient;
......@@ -35,6 +32,7 @@ type
private
FLoading: Boolean;
[async] procedure GetUnits;
procedure HandleListClick(e: TJSMouseEvent);
public
end;
......@@ -49,36 +47,76 @@ implementation
procedure TFViewUnits.WebFormCreate(Sender: TObject);
begin
console.log('Units.WebFormCreate: Starting setup...');
DMConnection.ApiConnection.Connected := True;
console.log('API connection active:', DMConnection.ApiConnection.Connected);
Document.addEventListener('click', @HandleListClick);
tmrRefresh.Enabled := False;
GetUnits;
tmrRefresh.Enabled := True;
// {$IFNDEF WIN32}
// asm
// var root = pas.TFViewUnits(Self).dblUnitsList.ElementHandle;
// if (root && !root.__emiDelegated) {
// root.__emiDelegated = true;
//
// root.addEventListener('click', function (e) {
// // Look for a click on, or inside, the details button
// var btn = e.target && e.target.closest('.btn-unit-details');
// if (!btn || !root.contains(btn)) return;
//
// e.preventDefault();
// e.stopPropagation();
//
// var unitId = btn.getAttribute('data-unitid') || '';
// pas.TFViewUnits(Self).OpenUnitDetails(unitId);
// }, { passive: true });
// }
// end;
// {$ENDIF}
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);
}
};
}
end;
end;
procedure TFViewUnits.HandleListClick(e: TJSMouseEvent);
var
el: TJSElement;
btn: TJSElement;
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
else
begin
btn := nil;
asm
btn = (el && el.closest) ? el.closest('.btn-unit-map') : null;
end;
if (btn <> nil) and (btn is TJSHtmlElement) then
begin
unitId := string(TJSHtmlElement(btn).getAttribute('data-unitid'));
e.preventDefault;
e.stopPropagation;
asm
try {
pas['View.Main'].FViewMain.ShowMapFocusUnit(unitId);
} catch (e) {
console.log('ShowMapFocusUnit failed', e);
}
end;
end;
end;
end;
procedure TFViewUnits.btnRefreshClick(Sender: TObject);
begin
GetUnits;
......@@ -122,6 +160,7 @@ begin
end;
end;
finally
FLoading := False;
Utils.HideSpinner('spinner');
console.log('GetUnits complete');
end;
......
/* --- TMS WEB Core Specific Fixes --- */
/* Removes the default border from the main Form wrapper */
span.card {
border: none;
}
/* --- Login Screen Styling --- */
.login-card {
display: inline-block;
width: 300px; /* Adjust width as needed */
display: inline-block; /* Or use d-flex on the parent to center it */
width: 100%;
max-width: 350px; /* Better than fixed 300px */
padding: 0;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
.mr-2 {
margin-right: 0.5rem;
}
.table tbody tr:hover {
background-color: #d1e7fd; /* Light blue color for hover effect */
cursor: pointer;
}
.form-input{
display: table;
}
.form-cells{
display: table-cell
}
/* Optional: Custom navbar look for the login screen */
.login-navbar {
max-width: 1200px; /* Set the max-width to match a medium screen */
max-width: 1200px;
margin: auto;
border-bottom-left-radius: 10px; /* Round the bottom left corner */
border-bottom-right-radius: 10px; /* Round the bottom right corner */
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
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 */
/* --- Table Customization --- */
/* Bootstrap has .table-hover, but this sets your specific blue color */
.table tbody tr:hover {
background-color: #d1e7fd;
cursor: pointer;
}
/* Style for the selected number */
.selected-number .page-link {
background-color: #204d74;
color: #fff !important;
}
/* --- Pagination Theme Overrides --- */
/* These override Bootstrap's blue to your specific darker blue (#204d74) */
/* Style for the unselected numbers and text (previous/next) */
.pagination .page-item a,
.pagination .page-item span {
color: #204d74;
.pagination .page-link {
color: #204d74; /* Text color for standard links */
}
.pagination .page-item.active .page-link,
.pagination .page-item.active .page-link:hover,
.pagination .page-item.active .page-link:focus {
/* Active State (Selected Page) */
.pagination .page-item.active .page-link {
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;
/* Hover State */
.pagination .page-item:not(.active) .page-link:hover {
background-color: #e9ecef; /* Standard Bootstrap light grey hover */
color: #16344a; /* Darker text on hover */
}
.modal {
z-index: 1055 !important;
/* --- Utilities --- */
.list-section-header:empty {
display: none;
}
#map_pnlmap > span {
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
position: absolute !important;
}
.list-section-header:empty { display: none; }
html, body {
height: 100%;
overflow: hidden;
}
.tab-hidden { display: none !important; }
.summary-chevron-icon {
width: 1rem;
height: 1rem;
display: block;
transition: transform 0.15s ease-in-out;
}
/* Expanded => show DOWN (rotate base UP chevron) */
.summary-toggle[aria-expanded="true"] .summary-chevron-icon {
transform: rotate(180deg);
}
unit uMapFilters;
interface
uses
System.SysUtils, System.StrUtils,
JS, Web,
VCL.TMSFNCLeaflet, VCL.TMSFNCMaps, VCL.TMSFNCMapsCommonTypes;
type
TMapFilters = class
private
FMap: TTMSFNCLeaflet;
FShowUnits: Boolean;
FShowComplaints: Boolean;
FShowLocation: Boolean;
function GetCheckBoxChecked(const elementId: string; defaultValue: Boolean): Boolean;
procedure SetCheckBoxChecked(const elementId: string; checked: Boolean);
procedure SetText(const elementId: string; const value: string);
procedure ReadUi;
procedure ResetUi;
procedure UpdateSummary;
procedure ApplyToMap;
procedure HandleDocClick(e: TJSMouseEvent);
procedure HandleDocChange(e: TJSEvent);
public
constructor Create(AMap: TTMSFNCLeaflet);
procedure Init;
procedure Apply;
procedure Reset;
end;
implementation
{ TMapFilters }
constructor TMapFilters.Create(AMap: TTMSFNCLeaflet);
begin
inherited Create;
FMap := AMap;
FShowUnits := True;
FShowComplaints := True;
FShowLocation := False;
end;
procedure TMapFilters.Init;
begin
Document.addEventListener('click', @HandleDocClick);
Document.addEventListener('change', @HandleDocChange);
ResetUi;
ReadUi;
UpdateSummary;
ApplyToMap;
end;
procedure TMapFilters.Apply;
begin
ReadUi;
UpdateSummary;
ApplyToMap;
end;
procedure TMapFilters.Reset;
begin
ResetUi;
ReadUi;
UpdateSummary;
ApplyToMap;
end;
procedure TMapFilters.HandleDocClick(e: TJSMouseEvent);
var
el: TJSElement;
id: string;
begin
el := TJSElement(e.target);
if el = nil then
Exit;
// Note: This supports clicking inner icons/spans by walking up to a parent with an id
asm
while (el && !el.id) { el = el.parentElement; }
end;
if (el <> nil) and (el is TJSHtmlElement) then
begin
id := string(TJSHtmlElement(el).id);
if id = 'map_filters_apply' then
begin
e.preventDefault;
e.stopPropagation;
Apply;
Exit;
end;
if id = 'map_filters_reset' then
begin
e.preventDefault;
e.stopPropagation;
Reset;
Exit;
end;
end;
end;
procedure TMapFilters.HandleDocChange(e: TJSEvent);
begin
//Note: Summary text changes when toggles change
ReadUi;
UpdateSummary;
end;
procedure TMapFilters.ReadUi;
begin
FShowUnits := GetCheckBoxChecked('map_filter_units', True);
FShowComplaints := GetCheckBoxChecked('map_filter_complaints', True);
FShowLocation := GetCheckBoxChecked('map_filter_location', False);
end;
procedure TMapFilters.ResetUi;
begin
SetCheckBoxChecked('map_filter_units', True);
SetCheckBoxChecked('map_filter_complaints', True);
SetCheckBoxChecked('map_filter_location', False);
end;
procedure TMapFilters.UpdateSummary;
var
summaryText: string;
begin
summaryText := '';
if FShowUnits then
summaryText := 'Units';
if FShowComplaints then
begin
if summaryText <> '' then
summaryText := summaryText + ', ';
summaryText := summaryText + 'Complaints';
end;
if FShowLocation then
begin
if summaryText <> '' then
summaryText := summaryText + ', ';
summaryText := summaryText + 'Location';
end;
if summaryText = '' then
summaryText := 'None';
SetText('map_filters_summary', summaryText);
end;
procedure TMapFilters.ApplyToMap;
var
i: Integer;
m: TTMSFNCMapsMarker;
ds: string;
showMarker: Boolean;
begin
if FMap = nil then
Exit;
FMap.BeginUpdate;
try
for i := 0 to FMap.Markers.Count - 1 do
begin
m := FMap.Markers[i];
ds := Trim(string(m.DataString));
// Note: Map form sets:
// - units: m.DataString := 'unit'
// - complaints: m.DataString := 'complaint'
showMarker := True;
if SameText(ds, 'unit') then
showMarker := FShowUnits
else if StartsText('complaint', LowerCase(ds)) then
showMarker := FShowComplaints
else if SameText(ds, 'device') then
showMarker := FShowLocation;
// Note: TTMSFNCMapsMarker supports visibility toggling
m.Visible := showMarker;
end;
finally
FMap.EndUpdate;
end;
end;
function TMapFilters.GetCheckBoxChecked(const elementId: string; defaultValue: Boolean): Boolean;
var
el: TJSElement;
begin
Result := defaultValue;
el := Document.getElementById(elementId);
if (el <> nil) and (el is TJSHtmlInputElement) then
Result := TJSHtmlInputElement(el).checked;
end;
procedure TMapFilters.SetCheckBoxChecked(const elementId: string; checked: Boolean);
var
el: TJSElement;
begin
el := Document.getElementById(elementId);
if (el <> nil) and (el is TJSHtmlInputElement) then
TJSHtmlInputElement(el).checked := checked;
end;
procedure TMapFilters.SetText(const elementId: string; const value: string);
var
el: TJSElement;
begin
el := Document.getElementById(elementId);
if (el <> nil) and (el is TJSHtmlElement) then
TJSHtmlElement(el).innerText := value;
end;
end.
......@@ -21,7 +21,9 @@ uses
View.EditUser in 'View.EditUser.pas' {FViewEditUser: TWebForm} {*.html},
Utils in 'Utils.pas',
View.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html},
View.ComplaintDetails in 'View.ComplaintDetails.pas' {FViewComplaintDetails: TWebForm} {*.html};
View.ComplaintDetails in 'View.ComplaintDetails.pas' {FViewComplaintDetails: TWebForm} {*.html},
View.UnitDetails in 'View.UnitDetails.pas' {FViewUnitDetails: TWebForm} {*.html},
uMapFilters in 'uMapFilters.pas';
{$R *.res}
......
......@@ -182,11 +182,18 @@
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.UnitDetails.pas">
<Form>FViewUnitDetails</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="uMapFilters.pas"/>
<None Include="index.html"/>
<None Include="css\app.css"/>
<None Include="css\spinner.css"/>
<None Include="assets\bpddistricts-updated.geojson"/>
<None Include="assets\bpddistricts.geojson"/>
<None Include="config\config.json"/>
<RcItem Include="assets\markers\alarm_1.png">
<ResourceType>RCDATA</ResourceType>
<ResourceId>PngImage_1</ResourceId>
......@@ -479,7 +486,6 @@
<ResourceType>RCDATA</ResourceType>
<ResourceId>PngImage_73</ResourceId>
</RcItem>
<None Include="config\config.json"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
......
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