Commit d8438015 by Mac Stephens

expand complaint details data model and endpoints (updated uqComplaintDetails,…

expand complaint details data model and endpoints (updated uqComplaintDetails, added history/contacts/warnings queries + endpoints) and refactor client ComplaintDetails to tabbed DB tables (remarks/history/contacts/warnings) with memo-type toggle filters. Fix navigation/JS bridge routing via View.Main, correct form injection/scroll behavior, and add sticky summary + controls with loading spinners for tab fetches.
parent 5a8f87eb
...@@ -21,3 +21,5 @@ emiMobileServer/logs/* ...@@ -21,3 +21,5 @@ emiMobileServer/logs/*
*.tvsconfig *.tvsconfig
*.dxsettings *.dxsettings
*.zip
...@@ -2,12 +2,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -2,12 +2,8 @@ object ApiDatabaseModule: TApiDatabaseModule
Height = 491 Height = 491
Width = 640 Width = 640
object OracleUniProvider1: TOracleUniProvider object OracleUniProvider1: TOracleUniProvider
Left = 182 Left = 164
Top = 98 Top = 38
end
object uqBooking: TUniQuery
Left = 350
Top = 98
end end
object uqMapUnits: TUniQuery object uqMapUnits: TUniQuery
Connection = ucENTCAD Connection = ucENTCAD
...@@ -31,8 +27,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -31,8 +27,8 @@ object ApiDatabaseModule: TApiDatabaseModule
'LEFT JOIN CD_UNITSTATUS cus ON cus.CODE = cfs.UNITSTATUS' 'LEFT JOIN CD_UNITSTATUS cus ON cus.CODE = cfs.UNITSTATUS'
'') '')
ReadOnly = True ReadOnly = True
Left = 470 Left = 464
Top = 414 Top = 390
object uqMapUnitsENTRYID: TFloatField object uqMapUnitsENTRYID: TFloatField
FieldName = 'ENTRYID' FieldName = 'ENTRYID'
end end
...@@ -129,8 +125,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -129,8 +125,8 @@ object ApiDatabaseModule: TApiDatabaseModule
'' ''
'') '')
ReadOnly = True ReadOnly = True
Left = 278 Left = 82
Top = 322 Top = 186
object uqUnitListUNITID: TFloatField object uqUnitListUNITID: TFloatField
FieldName = 'UNITID' FieldName = 'UNITID'
end end
...@@ -245,8 +241,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -245,8 +241,8 @@ object ApiDatabaseModule: TApiDatabaseModule
'WHERE ca.COMPLAINTID = :COMPLAINTID' 'WHERE ca.COMPLAINTID = :COMPLAINTID'
'ORDER BY ca.DATEDISPATCHED') 'ORDER BY ca.DATEDISPATCHED')
ReadOnly = True ReadOnly = True
Left = 470 Left = 466
Top = 356 Top = 326
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
...@@ -294,8 +290,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -294,8 +290,8 @@ object ApiDatabaseModule: TApiDatabaseModule
'WHERE cm.CFSID = :CFSID' 'WHERE cm.CFSID = :CFSID'
'ORDER BY cm.TIMESTAMP ASC') 'ORDER BY cm.TIMESTAMP ASC')
ReadOnly = True ReadOnly = True
Left = 278 Left = 196
Top = 382 Top = 248
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
...@@ -379,8 +375,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -379,8 +375,8 @@ object ApiDatabaseModule: TApiDatabaseModule
'') '')
ReadOnly = True ReadOnly = True
OnCalcFields = uqComplaintListCalcFields OnCalcFields = uqComplaintListCalcFields
Left = 94 Left = 78
Top = 320 Top = 242
object uqComplaintListCOMPLAINTID: TFloatField object uqComplaintListCOMPLAINTID: TFloatField
FieldName = 'COMPLAINTID' FieldName = 'COMPLAINTID'
Required = True Required = True
...@@ -525,20 +521,40 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -525,20 +521,40 @@ object ApiDatabaseModule: TApiDatabaseModule
' ct.DATEDISPATCHED,' ' ct.DATEDISPATCHED,'
' ct.DATERESPONDED,' ' ct.DATERESPONDED,'
' ct.DATEARRIVED,' ' ct.DATEARRIVED,'
' ct.DATECLEARED' ' 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'
'FROM COMPLAINT_ACTIVE ca' 'FROM COMPLAINT_ACTIVE ca'
'JOIN COMPLAINT_TIMES ct ON ca.COMPLAINTID = ct.COMPLAINTID' 'JOIN COMPLAINT_TIMES ct'
'LEFT JOIN CD_DISPATCHCODES cdc ON ca.DISPATCHCODE = cdc.CODE' ' ON ca.COMPLAINTID = ct.COMPLAINTID'
'WHERE ca.COMPLAINTID = :COMPLAINTID;' 'LEFT JOIN CD_DISPATCHCODES cdc'
'') ' ON ca.DISPATCHCODE = cdc.CODE'
'LEFT JOIN ADDRESS a'
' ON ca.ADDRESSID = a.ADDRESSID'
'WHERE ca.COMPLAINTID = :COMPLAINTID;')
ReadOnly = True ReadOnly = True
Left = 92 Left = 80
Top = 376 Top = 302
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
Name = 'COMPLAINTID' Name = 'COMPLAINTID'
Value = Null Value = nil
end> end>
object uqComplaintDetailsCOMPLAINTID: TFloatField object uqComplaintDetailsCOMPLAINTID: TFloatField
FieldName = 'COMPLAINTID' FieldName = 'COMPLAINTID'
...@@ -596,6 +612,49 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -596,6 +612,49 @@ object ApiDatabaseModule: TApiDatabaseModule
FieldName = 'DATECLEARED' FieldName = 'DATECLEARED'
ReadOnly = True ReadOnly = True
end end
object uqComplaintDetailsHISTORY: TFloatField
FieldName = 'HISTORY'
end
object uqComplaintDetailsCONTACTS: TFloatField
FieldName = 'CONTACTS'
end
object uqComplaintDetailsWARNINGS: TFloatField
FieldName = 'WARNINGS'
end
object uqComplaintDetailsADDRESSID: TFloatField
FieldName = 'ADDRESSID'
end
object uqComplaintDetailsAGENCY: TStringField
FieldName = 'AGENCY'
Size = 6
end
object uqComplaintDetailsSTRNUMBER: TFloatField
FieldName = 'STRNUMBER'
ReadOnly = True
end
object uqComplaintDetailsSTRHNUMBER: TStringField
FieldName = 'STRHNUMBER'
ReadOnly = True
Size = 1
end
object uqComplaintDetailsSTRPREFIX: TStringField
FieldName = 'STRPREFIX'
ReadOnly = True
Size = 1
end
object uqComplaintDetailsSTRNAME: TStringField
FieldName = 'STRNAME'
ReadOnly = True
end
object uqComplaintDetailsSTRSUFFIX: TStringField
FieldName = 'STRSUFFIX'
ReadOnly = True
Size = 2
end
object uqComplaintDetailsCITY: TStringField
FieldName = 'CITY'
ReadOnly = True
end
end end
object ucENTCAD: TUniConnection object ucENTCAD: TUniConnection
ProviderName = 'Oracle' ProviderName = 'Oracle'
...@@ -604,8 +663,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -604,8 +663,8 @@ object ApiDatabaseModule: TApiDatabaseModule
Server = 'BUFENTCAD' Server = 'BUFENTCAD'
Connected = True Connected = True
LoginPrompt = False LoginPrompt = False
Left = 38 Left = 50
Top = 98 Top = 36
EncryptedPassword = 'BAFFB1FFABFFBCFFBEFFBBFF' EncryptedPassword = 'BAFFB1FFABFFBCFFBEFFBBFF'
end end
object uqMapComplaints: TUniQuery object uqMapComplaints: TUniQuery
...@@ -651,8 +710,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -651,8 +710,8 @@ object ApiDatabaseModule: TApiDatabaseModule
'') '')
ReadOnly = True ReadOnly = True
OnCalcFields = uqMapComplaintsCalcFields OnCalcFields = uqMapComplaintsCalcFields
Left = 470 Left = 468
Top = 296 Top = 264
object uqMapComplaintsCOMPLAINTID: TFloatField object uqMapComplaintsCOMPLAINTID: TFloatField
FieldName = 'COMPLAINTID' FieldName = 'COMPLAINTID'
Required = True Required = True
...@@ -710,8 +769,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -710,8 +769,8 @@ object ApiDatabaseModule: TApiDatabaseModule
'from dual;' 'from dual;'
'') '')
ReadOnly = True ReadOnly = True
Left = 194 Left = 198
Top = 270 Top = 190
object uqBadgeCountsCOMPLAINTS: TFloatField object uqBadgeCountsCOMPLAINTS: TFloatField
FieldName = 'COMPLAINTS' FieldName = 'COMPLAINTS'
ReadOnly = True ReadOnly = True
...@@ -741,9 +800,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -741,9 +800,8 @@ object ApiDatabaseModule: TApiDatabaseModule
' AND c.YCOORD IS NOT NULL' ' AND c.YCOORD IS NOT NULL'
')' ')'
'ORDER BY ca.COMPLAINTID, ca.DATEDISPATCHED;') 'ORDER BY ca.COMPLAINTID, ca.DATEDISPATCHED;')
Active = True Left = 464
Left = 470 Top = 202
Top = 242
object uqMapComplaintUnitsListCOMPLAINTID: TFloatField object uqMapComplaintUnitsListCOMPLAINTID: TFloatField
FieldName = 'COMPLAINTID' FieldName = 'COMPLAINTID'
end end
...@@ -771,4 +829,152 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -771,4 +829,152 @@ object ApiDatabaseModule: TApiDatabaseModule
Size = 30 Size = 30
end end
end end
object uqComplaintHistory: TUniQuery
Connection = ucENTCAD
SQL.Strings = (
'with ctx as ('
' select'
' ca.agency,'
' a.strnumber,'
' a.strhnumber,'
' a.strprefix,'
' a.strname,'
' a.strsuffix,'
' a.city'
' from complaint_active ca'
' left join address a on ca.addressid = a.addressid'
' where ca.complaintid = :COMPLAINTID'
')'
'select'
' c.complaint,'
' c.apartment,'
' c.datereported,'
' cp.code_desc as dpriority,'
' cdc.code_desc as dcalltype'
'from complaint_archive c'
'join ctx on 1 = 1'
'left join cd_callpriorities cp on c.priority = cp.code'
'left join cd_dispatchcodes cdc on c.dispatchcode = cdc.code'
'where c.agency = ctx.agency'
'and ('
' (ctx.strnumber is null and c.strnumber is null)'
' or (ctx.strnumber is not null and c.strnumber = ctx.strnumber)'
')'
'and (ctx.strhnumber is null or c.strhnumber = ctx.strhnumber)'
'and (ctx.strprefix is null or c.strprefix = ctx.strprefix)'
'and (ctx.strname is null or c.strname = ctx.strname)'
'and (ctx.strsuffix is null or c.strsuffix = ctx.strsuffix)'
'and (ctx.city is null or c.city = ctx.city)'
'order by c.datereported desc')
Left = 328
Top = 72
ParamData = <
item
DataType = ftUnknown
Name = 'COMPLAINTID'
Value = nil
end>
end
object uqComplaintContacts: TUniQuery
Connection = ucENTCAD
SQL.Strings = (
'with ctx as ('
' select ca.addressid'
' from complaint_active ca'
' where ca.complaintid = :COMPLAINTID'
')'
'select'
' c.name,'
' c.phone,'
' ct.code_desc as dcontacttype,'
' c.remarks'
'from dis_contact c'
'join ctx on c.addressid = ctx.addressid'
'left join cd_contact_type ct on c.contact_type = ct.code')
Left = 330
Top = 130
ParamData = <
item
DataType = ftUnknown
Name = 'COMPLAINTID'
Value = nil
end>
end
object uqComplaintWarnings: TUniQuery
Connection = ucENTCAD
SQL.Strings = (
'with ctx as ('
' select ca.agency, ca.addressid'
' from complaint_active ca'
' where ca.complaintid = :COMPLAINTID'
'),'
'sys as ('
' select s.warningdistance'
' from system s'
' join ctx on s.agency = ctx.agency'
'),'
'adr as ('
' select a.xcoord, a.ycoord'
' from address a'
' join ctx on a.addressid = ctx.addressid'
')'
'select'
' wt.code_desc,'
' trim('
' nvl(a.strnumber, '#39#39') || '#39' '#39' ||'
' nvl(a.strhnumber, '#39#39') || '#39' '#39' ||'
' nvl(a.strprefix, '#39#39') || '#39' '#39' ||'
' nvl(a.strname, '#39#39') || '#39' '#39' ||'
' nvl(a.strsuffix, '#39#39') ||'
' case when a.apartment is not null then '#39' '#39' || a.apartment el' +
'se '#39#39' end ||'
' case when a.city is not null then '#39', '#39' || a.city else '#39#39' end'
' ) as address_text,'
' case'
' when w.addressid <> (select addressid from ctx) then '#39'[SECON' +
'DARY ADDRESS] - '#39' || nvl(w.notes, '#39#39')'
' else nvl(w.notes, '#39#39')'
' end as notes,'
' abs(sqrt(power(a.xcoord - (select xcoord from adr), 2) + power' +
'(a.ycoord - (select ycoord from adr), 2))) as distance'
'from dis_warning w'
'join cd_warningtype wt on w.code = wt.code'
'join address a on w.addressid = a.addressid'
'where w.status = 1'
'and ('
' a.addressid = (select addressid from ctx)'
' or ('
' (select xcoord from adr) > 0'
' and (select ycoord from adr) > 0'
' and nvl((select warningdistance from sys), 0) > 0'
' and a.xcoord between (select xcoord from adr) - (select warn' +
'ingdistance from sys)'
' and (select xcoord from adr) + (select warnin' +
'gdistance from sys)'
' and a.ycoord between (select ycoord from adr) - (select warn' +
'ingdistance from sys)'
' and (select ycoord from adr) + (select warnin' +
'gdistance from sys)'
' )'
')'
'order by'
' distance,'
' decode(w.code, '#39'POL'#39', 1, '#39'FIR'#39', 2, 3),'
' wt.code_desc')
Left = 488
Top = 76
ParamData = <
item
DataType = ftUnknown
Name = 'COMPLAINTID'
Value = nil
end>
end
end end
...@@ -10,7 +10,6 @@ uses ...@@ -10,7 +10,6 @@ uses
type type
TApiDatabaseModule = class(TDataModule) TApiDatabaseModule = class(TDataModule)
OracleUniProvider1: TOracleUniProvider; OracleUniProvider1: TOracleUniProvider;
uqBooking: TUniQuery;
uqMapUnits: TUniQuery; uqMapUnits: TUniQuery;
uqUnitList: TUniQuery; uqUnitList: TUniQuery;
uqComplaintUnits: TUniQuery; uqComplaintUnits: TUniQuery;
...@@ -131,6 +130,20 @@ type ...@@ -131,6 +130,20 @@ type
uqMapUnitsCALL_TYPE: TStringField; uqMapUnitsCALL_TYPE: TStringField;
uqMapUnitsPRIORITY: TStringField; uqMapUnitsPRIORITY: TStringField;
uqMapUnitsUNIT_STATUS_DESC: 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;
procedure uqComplaintListCalcFields(DataSet: TDataSet); procedure uqComplaintListCalcFields(DataSet: TDataSet);
procedure uqMapComplaintsCalcFields(DataSet: TDataSet); procedure uqMapComplaintsCalcFields(DataSet: TDataSet);
private private
......
...@@ -22,6 +22,11 @@ type ...@@ -22,6 +22,11 @@ type
[HttpGet] function GetComplaintMap: TJSONObject; [HttpGet] function GetComplaintMap: TJSONObject;
[HttpGet] function GetUnitMap: TJSONObject; [HttpGet] function GetUnitMap: TJSONObject;
[HttpGet] function GetComplaintDetails(const ComplaintId: string): 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;
end; end;
implementation implementation
......
...@@ -16,6 +16,7 @@ type ...@@ -16,6 +16,7 @@ type
private private
procedure AfterConstruction; override; procedure AfterConstruction; override;
procedure BeforeDestruction; override; procedure BeforeDestruction; override;
function GetComplaintMemos(const CfsId: string): TJSONObject;
public public
function GetBadgeCounts: TJSONObject; function GetBadgeCounts: TJSONObject;
function GetComplaintList: TJSONObject; function GetComplaintList: TJSONObject;
...@@ -23,6 +24,9 @@ type ...@@ -23,6 +24,9 @@ type
function GetComplaintMap: TJSONObject; function GetComplaintMap: TJSONObject;
function GetUnitMap: TJSONObject; function GetUnitMap: TJSONObject;
function GetComplaintDetails(const ComplaintId: string): TJSONObject; function GetComplaintDetails(const ComplaintId: string): TJSONObject;
function GetComplaintHistory(const ComplaintId: string): TJSONObject;
function GetComplaintContacts(const ComplaintId: string): TJSONObject;
function GetComplaintWarnings(const ComplaintId: string): TJSONObject;
end; end;
implementation implementation
...@@ -66,7 +70,7 @@ begin ...@@ -66,7 +70,7 @@ begin
except except
on E: Exception do on E: Exception do
begin begin
Logger.Log(3, '---TApiService.GetBadgeCounts End (error): ' + E.Message); Logger.Log(2, '---TApiService.GetBadgeCounts End (error): ' + E.Message);
raise EXDataHttpException.Create(500, 'Failed to load badge counts'); raise EXDataHttpException.Create(500, 'Failed to load badge counts');
end; end;
end; end;
...@@ -176,7 +180,7 @@ begin ...@@ -176,7 +180,7 @@ begin
on E: Exception do on E: Exception do
begin begin
FreeAndNil(data); FreeAndNil(data);
Logger.Log(1,'GetComplaintMap error: '+E.Message); Logger.Log(2,'GetComplaintMap error: '+E.Message);
raise EXDataHttpException.Create(500,'Failed to load complaint map'); raise EXDataHttpException.Create(500,'Failed to load complaint map');
end; end;
end; end;
...@@ -233,7 +237,7 @@ begin ...@@ -233,7 +237,7 @@ begin
Result.AddPair('data', data); Result.AddPair('data', data);
except except
data.Free; data.Free;
Logger.Log(3, '---TApiService.GetUnitMap End (error)'); Logger.Log(2, '---TApiService.GetUnitMap End (error)');
raise EXDataHttpException.Create(500, 'Failed to load unit map'); raise EXDataHttpException.Create(500, 'Failed to load unit map');
end; end;
...@@ -310,7 +314,7 @@ begin ...@@ -310,7 +314,7 @@ begin
Result.AddPair('data', data); Result.AddPair('data', data);
except except
data.Free; data.Free;
Logger.Log(3, '---TApiService.GetComplaintList End (error)'); Logger.Log(2, '---TApiService.GetComplaintList End (error)');
raise EXDataHttpException.Create(500, 'Failed to load complaints list'); raise EXDataHttpException.Create(500, 'Failed to load complaints list');
end; end;
...@@ -423,35 +427,50 @@ begin ...@@ -423,35 +427,50 @@ begin
if Eof then raise EXDataHttpException.Create(404,'Complaint not found'); if Eof then raise EXDataHttpException.Create(404,'Complaint not found');
obj := TJSONObject.Create; obj := TJSONObject.Create;
obj.AddPair('ComplaintId',FieldByName('COMPLAINTID').AsString); obj.AddPair('ComplaintId', ApiDB.uqComplaintDetailsCOMPLAINTID.AsString);
obj.AddPair('CFSId',FieldByName('CFSID').AsString); obj.AddPair('CFSId', ApiDB.uqComplaintDetailsCFSID.AsString);
obj.AddPair('Complaint',FieldByName('COMPLAINT').AsString); obj.AddPair('Complaint', ApiDB.uqComplaintDetailsCOMPLAINT.AsString);
obj.AddPair('Priority',FieldByName('PRIORITY').AsString); obj.AddPair('Priority', ApiDB.uqComplaintDetailsPRIORITY.AsString);
obj.AddPair('DispatchCode',FieldByName('DISPATCHCODE').AsString); obj.AddPair('DispatchCode', ApiDB.uqComplaintDetailsDISPATCHCODE.AsString);
obj.AddPair('DispatchCodeDesc',FieldByName('DISPATCH_CODE_DESC').AsString); obj.AddPair('DispatchCodeDesc', ApiDB.uqComplaintDetailsDISPATCH_CODE_DESC.AsString);
obj.AddPair('DispatchDistrict',FieldByName('DISPATCHDISTRICT').AsString); obj.AddPair('DispatchDistrict', ApiDB.uqComplaintDetailsDISPATCHDISTRICT.AsString);
obj.AddPair('Address',FieldByName('ADDRESS').AsString); obj.AddPair('Address', ApiDB.uqComplaintDetailsADDRESS.AsString);
obj.AddPair('History', ApiDB.uqComplaintDetailsHISTORY.AsString);
if FieldByName('DATEREPORTED').IsNull obj.AddPair('Contacts', ApiDB.uqComplaintDetailsCONTACTS.AsString);
then obj.AddPair('DateReported','') obj.AddPair('Warnings', ApiDB.uqComplaintDetailsWARNINGS.AsString);
else obj.AddPair('DateReported',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATEREPORTED') as TDateTimeField).AsDateTime));
if FieldByName('DATERECEIVED').IsNull if ApiDB.uqComplaintDetailsDATEREPORTED.IsNull then
then obj.AddPair('DateReceived','') obj.AddPair('DateReported', '')
else obj.AddPair('DateReceived',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATERECEIVED') as TDateTimeField).AsDateTime)); else
if FieldByName('DATEDISPATCHED').IsNull obj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEREPORTED.AsDateTime));
then obj.AddPair('DateDispatched','')
else obj.AddPair('DateDispatched',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATEDISPATCHED') as TDateTimeField).AsDateTime)); if ApiDB.uqComplaintDetailsDATERECEIVED.IsNull then
if FieldByName('DATERESPONDED').IsNull obj.AddPair('DateReceived', '')
then obj.AddPair('DateResponded','') else
else obj.AddPair('DateResponded',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATERESPONDED') as TDateTimeField).AsDateTime)); obj.AddPair('DateReceived', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATERECEIVED.AsDateTime));
if FieldByName('DATEARRIVED').IsNull
then obj.AddPair('DateArrived','') if ApiDB.uqComplaintDetailsDATEDISPATCHED.IsNull then
else obj.AddPair('DateArrived',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATEARRIVED') as TDateTimeField).AsDateTime)); obj.AddPair('DateDispatched', '')
if FieldByName('DATECLEARED').IsNull else
then obj.AddPair('DateCleared','') obj.AddPair('DateDispatched', FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqComplaintDetailsDATEDISPATCHED.AsDateTime));
else obj.AddPair('DateCleared',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATECLEARED') as TDateTimeField).AsDateTime));
if ApiDB.uqComplaintDetailsDATERESPONDED.IsNull then
Result.AddPair('data',obj); 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 finally
Close; Close;
end; end;
...@@ -472,6 +491,246 @@ begin ...@@ -472,6 +491,246 @@ begin
end; end;
function TApiService.GetComplaintMemos(const CfsId: string): TJSONObject;
var
data: TJSONArray;
item: TJSONObject;
ts: string;
begin
Logger.Log(3, '---TApiService.GetComplaintMemos initiated: ' + CfsId);
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
data := TJSONArray.Create;
try
with ApiDB.uqCFSMemos do
begin
ParamByName('CFSID').AsString := CfsId;
Open;
try
First;
while not Eof do
begin
item := TJSONObject.Create;
item.AddPair('MemoId', ApiDB.uqCFSMemosMEMO_ID.AsString);
item.AddPair('CFSId', ApiDB.uqCFSMemosCFSID.AsString);
item.AddPair('MemoType', ApiDB.uqCFSMemosMEMO_TYPE.AsString);
if ApiDB.uqCFSMemosTIMESTAMP.IsNull then
ts := ''
else
ts := FormatDateTime('yyyy-mm-dd hh:nn:ss', ApiDB.uqCFSMemosTIMESTAMP.AsDateTime);
item.AddPair('Timestamp', ts);
item.AddPair('BadgeNumber', ApiDB.uqCFSMemosBADGE_NUMBER.AsString);
item.AddPair('Remarks', ApiDB.uqCFSMemosREMARKS.AsString);
data.AddElement(item);
Next;
end;
finally
Close;
end;
end;
Result.AddPair('count', TJSONNumber.Create(data.Count));
Result.AddPair('returned', TJSONNumber.Create(data.Count));
Result.AddPair('data', data);
except
data.Free;
Logger.Log(3, '---TApiService.GetComplaintMemos End (error)');
raise EXDataHttpException.Create(500, 'Failed to load complaint memos');
end;
Logger.Log(3, '---TApiService.GetComplaintMemos End');
end;
function TApiService.GetComplaintHistory(const ComplaintId: string): TJSONObject;
var
dataArr: TJSONArray;
rowObj: TJSONObject;
returnedCount: Integer;
begin
Logger.Log(4, '---TApiService.GetComplaintHistory initiated: ' + ComplaintId);
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
dataArr := TJSONArray.Create;
Result.AddPair('data', dataArr);
returnedCount := 0;
try
with ApiDB.uqComplaintHistory do
begin
ParamByName('COMPLAINTID').AsString := ComplaintId;
Open;
try
while not Eof do
begin
rowObj := TJSONObject.Create;
dataArr.AddElement(rowObj);
rowObj.AddPair('Complaint', FieldByName('COMPLAINT').AsString);
rowObj.AddPair('Apartment', FieldByName('APARTMENT').AsString);
if FieldByName('DATEREPORTED').IsNull then
rowObj.AddPair('DateReported', '')
else
rowObj.AddPair('DateReported', FormatDateTime('yyyy-mm-dd hh:nn:ss', FieldByName('DATEREPORTED').AsDateTime));
rowObj.AddPair('DPriority', FieldByName('DPRIORITY').AsString);
rowObj.AddPair('DCallType', FieldByName('DCALLTYPE').AsString);
Inc(returnedCount);
Next;
end;
finally
Close;
end;
end;
Result.AddPair('count', TJSONNumber.Create(returnedCount));
Result.AddPair('returned', TJSONNumber.Create(returnedCount));
Logger.Log(3, '---TApiService.GetComplaintHistory End (returned=' + IntToStr(returnedCount) + ')');
except
on E: EXDataHttpException do
begin
Logger.Log(2, '---TApiService.GetComplaintHistory http error: ' + E.Message);
raise;
end;
on E: Exception do
begin
Logger.Log(2, '---TApiService.GetComplaintHistory error: ' + E.Message);
raise EXDataHttpException.Create(500, 'Failed to load complaint history');
end;
end;
end;
function TApiService.GetComplaintContacts(const ComplaintId: string): TJSONObject;
var
dataArr: TJSONArray;
rowObj: TJSONObject;
returnedCount: Integer;
begin
Logger.Log(4, '---TApiService.GetComplaintContacts initiated: ' + ComplaintId);
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
dataArr := TJSONArray.Create;
Result.AddPair('data', dataArr);
returnedCount := 0;
try
with ApiDB.uqComplaintContacts do
begin
ParamByName('COMPLAINTID').AsString := ComplaintId;
Open;
try
while not Eof do
begin
rowObj := TJSONObject.Create;
dataArr.AddElement(rowObj);
rowObj.AddPair('Name', FieldByName('NAME').AsString);
rowObj.AddPair('Phone', FieldByName('PHONE').AsString);
rowObj.AddPair('DContactType', FieldByName('DCONTACTTYPE').AsString);
rowObj.AddPair('Remarks', FieldByName('REMARKS').AsString);
Inc(returnedCount);
Next;
end;
finally
Close;
end;
end;
Result.AddPair('count', TJSONNumber.Create(returnedCount));
Result.AddPair('returned', TJSONNumber.Create(returnedCount));
Logger.Log(3, '---TApiService.GetComplaintContacts End (returned=' + IntToStr(returnedCount) + ')');
except
on E: EXDataHttpException do
begin
Logger.Log(2, '---TApiService.GetComplaintContacts http error: ' + E.Message);
raise;
end;
on E: Exception do
begin
Logger.Log(2, '---TApiService.GetComplaintContacts error: ' + E.Message);
raise EXDataHttpException.Create(500, 'Failed to load complaint contacts');
end;
end;
end;
function TApiService.GetComplaintWarnings(const ComplaintId: string): TJSONObject;
var
dataArr: TJSONArray;
rowObj: TJSONObject;
returnedCount: Integer;
begin
Logger.Log(3, '---TApiService.GetComplaintWarnings initiated: ' + ComplaintId);
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
dataArr := TJSONArray.Create;
Result.AddPair('data', dataArr);
returnedCount := 0;
try
with ApiDB.uqComplaintWarnings do
begin
ParamByName('COMPLAINTID').AsString := ComplaintId;
Open;
try
while not Eof do
begin
rowObj := TJSONObject.Create;
dataArr.AddElement(rowObj);
rowObj.AddPair('CodeDesc', FieldByName('CODE_DESC').AsString);
rowObj.AddPair('Address', FieldByName('ADDRESS_TEXT').AsString);
rowObj.AddPair('Notes', FieldByName('NOTES').AsString);
Inc(returnedCount);
Next;
end;
finally
Close;
end;
end;
Result.AddPair('count', TJSONNumber.Create(returnedCount));
Result.AddPair('returned', TJSONNumber.Create(returnedCount));
Logger.Log(3, '---TApiService.GetComplaintWarnings End (returned=' + IntToStr(returnedCount) + ')');
except
on E: EXDataHttpException do
begin
Logger.Log(2, '---TApiService.GetComplaintWarnings http error: ' + E.Message);
raise;
end;
on E: Exception do
begin
Logger.Log(2, '---TApiService.GetComplaintWarnings error: ' + E.Message);
raise EXDataHttpException.Create(500, 'Failed to load complaint warnings');
end;
end;
end;
initialization initialization
......
[Settings] [Settings]
LogFileNum=533 LogFileNum=551
webClientVersion=0.1.0 webClientVersion=0.1.0
<div class="row"> <div class="container-fluid p-3">
<div class="col-lg-12">
<h1 class="page-header" id="view.userprofile.title">Admin User Profile</h1> <h1 id="view.userprofile.title" class="h2 border-bottom pb-2 mb-4 text-primary">
Admin User Profile
<div role="form"> </h1>
<div class="form-group">
<label id="view.userprofile.form.lblUserName">User Name:</label> <div class="card shadow-sm mb-4">
<input id="view.userprofile.form.edtUserName" class="form-control"> <div class="card-body">
</div> <h5 class="card-title text-secondary mb-3">User Details</h5>
<div class="form-group">
<label id="view.userprofile.form.lblFullName">User Fullname:</label> <div class="row g-3">
<input id="view.userprofile.form.edtFullName" class="form-control">
</div> <div class="col-12">
<div class="form-group"> <label id="view.userprofile.form.lblFullName" for="view.userprofile.form.edtFullName" class="form-label fw-bold">User Fullname:</label>
<label id="view.userprofile.form.lblAgency">User Agency:</label> <input id="view.userprofile.form.edtFullName" type="text" class="form-control">
<input id="view.userprofile.form.edtAgency" class="form-control"> </div>
</div>
<div class="form-group"> <div class="col-md-6">
<label id="view.userprofile.form.lblBadgeNum">User Bage #:</label> <label id="view.userprofile.form.lblUserName" for="view.userprofile.form.edtUserName" class="form-label fw-bold">User Name:</label>
<input id="view.userprofile.form.edtBadgeNum" class="form-control"> <input id="view.userprofile.form.edtUserName" type="text" class="form-control">
</div> </div>
<div class="form-group"> <div class="col-md-6">
<label id="view.userprofile.form.lblUserId">User Id:</label> <label id="view.userprofile.form.lblAgency" for="view.userprofile.form.edtAgency" class="form-label fw-bold">User Agency:</label>
<input id="view.userprofile.form.edtUserId" class="form-control"> <input id="view.userprofile.form.edtAgency" type="text" class="form-control">
</div> </div>
<div class="form-group">
<label id="view.userprofile.form.lblPersonnelId">Personnel Id:</label> <div class="col-md-4">
<input id="view.userprofile.form.edtPersonnelId" class="form-control"> <label id="view.userprofile.form.lblBadgeNum" for="view.userprofile.form.edtBadgeNum" class="form-label small text-muted">User Badge #:</label>
</div> <input id="view.userprofile.form.edtBadgeNum" type="text" class="form-control">
<div class="custom-control custom-checkbox"> </div>
<input type="checkbox" class="custom-control-input" id="view.userprofile.form.chkAdminUser"> <div class="col-md-4">
<label class="custom-control-label" for="view.userprofile.form.chkAdminUser">Admin User</label> <label id="view.userprofile.form.lblUserId" for="view.userprofile.form.edtUserId" class="form-label small text-muted">User Id:</label>
</div> <input id="view.userprofile.form.edtUserId" type="text" class="form-control">
<div class="form-input"> </div>
<div><label id="lblinfo" class="py-2" style="font-size: 1.00rem;"></label></div> <div class="col-md-4">
<div><input class="form-control input-sm" id="edtusername" width='50%'/></div> <label id="view.userprofile.form.lblPersonnelId" for="view.userprofile.form.edtPersonnelId" class="form-label small text-muted">Personnel Id:</label>
<div class="py-2"><input class="form-control input-sm" id="edtpassword" width='50%'/></div> <input id="view.userprofile.form.edtPersonnelId" type="text" class="form-control">
<button id="btnadduser"></button> </div>
<div><label id="lblresult" class="py-2" style="font-size: 1.00rem;"></label></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>
<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>
</div>
</div> </div>
object FViewComplaintDetails: TFViewComplaintDetails object FViewComplaintDetails: TFViewComplaintDetails
Width = 640 Width = 800
Height = 480 Height = 672
CSSLibrary = cssBootstrap CSSLibrary = cssBootstrap
ElementFont = efCSS ElementFont = efCSS
object WebDBTableControl1: TWebDBTableControl object btnHistory: TWebButton
Left = 164 Left = 506
Top = 198 Top = 134
Width = 300 Width = 96
Height = 200 Height = 25
Caption = 'History'
ChildOrder = 1
ElementID = 'btn_history'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnHistoryClick
end
object btnWarnings: TWebButton
Left = 294
Top = 134
Width = 96
Height = 25
Caption = 'Warnings'
ChildOrder = 1
ElementID = 'btn_warnings'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnWarningsClick
end
object btnContacts: TWebButton
Left = 404
Top = 134
Width = 96
Height = 25
Caption = 'Contacts'
ChildOrder = 1
ElementID = 'btn_contacts'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnContactsClick
end
object btnCmp: TWebButton
Left = 190
Top = 165
Width = 96
Height = 25
Caption = 'CMP'
ChildOrder = 1
ElementID = 'btn_cmp'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnCmpClick
end
object btnE911: TWebButton
Left = 294
Top = 165
Width = 96
Height = 25
Caption = 'E-911'
ChildOrder = 1
ElementID = 'btn_e911'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnE911Click
end
object btnREM: TWebButton
Left = 404
Top = 165
Width = 96
Height = 25
Caption = 'REM'
ChildOrder = 1
ElementID = 'btn_rem'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnREMClick
end
object btnUnt: TWebButton
Left = 506
Top = 165
Width = 96
Height = 25
Caption = 'UNT'
ChildOrder = 1
ElementID = 'btn_unt'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnUntClick
end
object tblRemarks: TWebDBTableControl
Left = 8
Top = 196
Width = 191
Height = 147
ElementId = 'tbl_remarks'
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 = 'text-nowrap'
DataField = 'MemoType'
Title = 'Type'
end
item
ElementClassName = 'text-nowrap'
DataField = 'Timestamp'
Title = 'Timestamp'
end
item
DataField = 'Remarks'
Title = 'Remarks'
end>
DataSource = wdsRemarks
end
object tblContacts: TWebDBTableControl
Left = 399
Top = 196
Width = 191
Height = 147
ElementId = 'tbl_contacts'
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 = 'text-nowrap'
DataField = 'Name'
Title = 'Name'
end
item
ElementClassName = 'text-nowrap'
DataField = 'Phone'
Title = 'Phone'
end
item
ElementClassName = 'text-nowrap'
DataField = 'DContactType'
Title = 'Contact Type'
end
item
DataField = 'Remarks'
Title = 'Remarks'
end>
DataSource = wdsContacts
end
object tblHistory: TWebDBTableControl
Left = 202
Top = 196
Width = 191
Height = 147
ElementId = 'tbl_history'
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 = 'text-nowrap'
DataField = 'Complaint'
Title = 'Complaint'
end
item
ElementClassName = 'text-nowrap'
DataField = 'Apartment'
Title = 'Apartment'
end
item
ElementClassName = 'text-nowrap'
DataField = 'DateReported'
Title = 'Date Reported'
end
item
ElementClassName = 'text-nowrap'
DataField = 'DPriority'
Title = 'Priority'
end
item
DataField = 'DCallType'
Title = 'Call Type'
end>
DataSource = wdsHistory
end
object tblWarnings: TWebDBTableControl
Left = 596
Top = 196
Width = 191
Height = 147
ElementId = 'tbl_warnings'
BorderColor = clSilver BorderColor = clSilver
ChildOrder = 8
ElementFont = efCSS ElementFont = efCSS
ElementHeaderClassName = 'thead-light' ElementHeaderClassName = 'table-light'
ElementTableClassName = 'table table-striped table-bordered table-hover' ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
Footer.ButtonActiveElementClassName = 'btn btn-primary' Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light' Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control' Footer.DropDownElementClassName = 'form-control'
...@@ -30,6 +301,132 @@ object FViewComplaintDetails: TFViewComplaintDetails ...@@ -30,6 +301,132 @@ object FViewComplaintDetails: TFViewComplaintDetails
Header.ListElementClassName = 'pagination' Header.ListElementClassName = 'pagination'
Header.ListItemElementClassName = 'page-item' Header.ListItemElementClassName = 'page-item'
Header.ListLinkElementClassName = 'page-link' Header.ListLinkElementClassName = 'page-link'
Columns = <> WordWrap = True
Columns = <
item
ElementClassName = 'text-nowrap'
DataField = 'CodeDesc'
Title = 'Warning'
end
item
ElementClassName = 'text-nowrap'
DataField = 'Address'
Title = 'Address'
end
item
DataField = 'Notes'
Title = 'Notes'
end>
DataSource = wdsWarnings
end
object btnRemarks: TWebButton
Left = 190
Top = 134
Width = 96
Height = 25
Caption = 'Remarks'
ChildOrder = 1
ElementID = 'btn_remarks'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnRemarksClick
end
object xdwcComplaintDetails: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 378
Top = 424
end
object xdwdsRemarks: TXDataWebDataSet
Left = 40
Top = 350
object xdwdsRemarksMemoId: TStringField
FieldName = 'MemoId'
end
object xdwdsRemarksCFSId: TStringField
FieldName = 'CFSId'
end
object xdwdsRemarksMemoType: TStringField
FieldName = 'MemoType'
end
object xdwdsRemarksTimestamp: TStringField
FieldName = 'Timestamp'
end
object xdwdsRemarksBadgeNumber: TStringField
FieldName = 'BadgeNumber'
end
object xdwdsRemarksRemarks: TStringField
FieldName = 'Remarks'
end
end
object wdsRemarks: TWebDataSource
DataSet = xdwdsRemarks
Left = 126
Top = 350
end
object xdwdsHistory: TXDataWebDataSet
Left = 238
Top = 348
object xdwdsHistoryComplaint: TStringField
FieldName = 'Complaint'
end
object xdwdsHistoryApartment: TStringField
FieldName = 'Apartment'
end
object xdwdsHistoryDateReported: TStringField
FieldName = 'DateReported'
end
object xdwdsHistoryDPriority: TStringField
FieldName = 'DPriority'
end
object xdwdsHistoryDCallType: TStringField
FieldName = 'DCallType'
end
end
object wdsHistory: TWebDataSource
DataSet = xdwdsHistory
Left = 328
Top = 350
end
object xdwdsContacts: TXDataWebDataSet
Left = 436
Top = 352
object xdwdsContactsName: TStringField
FieldName = 'Name'
end
object xdwdsContactsPhone: TStringField
FieldName = 'Phone'
end
object xdwdsContactsDContactType: TStringField
FieldName = 'DContactType'
end
object xdwdsContactsRemarks: TStringField
FieldName = 'Remarks'
end
end
object wdsContacts: TWebDataSource
DataSet = xdwdsContacts
Left = 530
Top = 352
end
object xdwdsWarnings: TXDataWebDataSet
Left = 638
Top = 352
object xdwdsWarningsCodeDesc: TStringField
FieldName = 'CodeDesc'
end
object xdwdsWarningsAddress: TStringField
FieldName = 'Address'
end
object xdwdsWarningsNotes: TStringField
FieldName = 'Notes'
end
end
object wdsWarnings: TWebDataSource
DataSet = xdwdsWarnings
Left = 740
Top = 352
end end
end end
<!-- Sticky local navbar (Complaint Details) --> <div class="d-flex flex-column h-100 w-100 overflow-hidden">
<div class="sticky-top"> <div class="flex-grow-1 d-flex flex-column overflow-auto bg-light p-2 p-md-3" style="min-height:0;">
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid"> <!-- Sticky block: Summary header + (expanded) summary body + buttons -->
<div class="row w-100 g-2 align-items-stretch"> <div class="sticky-top bg-light" style="z-index:20;">
<div class="col">
<div id="cdetails_title" class="navbar-brand mb-0 h5 text-white">Complaint Details</div> <!-- Summary header -->
</div> <div class="card border-0 shadow-sm mb-2">
<div class="col-auto"> <div class="card-header bg-white py-2">
<button id="btn_close_complaint_details" type="button" class="btn btn-outline-light w-100 h-100"> <button
<i class="fa fa-times me-1"></i> class="btn btn-link text-decoration-none p-0 w-100 d-flex align-items-center justify-content-between"
<span class="d-none d-sm-inline">Close</span> type="button"
data-bs-toggle="collapse"
data-bs-target="#cdetails_summary"
aria-expanded="false"
aria-controls="cdetails_summary">
<span class="fw-semibold text-dark" id="lbl_summary_title">Summary</span>
<i class="fa fa-chevron-down"></i>
</button> </button>
</div> </div>
</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 --> <!-- Summary body (stays sticky because it's inside the sticky block) -->
<div class="card border-0 shadow-sm"> <div id="cdetails_summary" class="collapse">
<div class="card-body p-3 bg-light"> <div class="card border-0 shadow-sm mb-2">
<div class="card-body py-2">
<div class="row gy-1 gx-2">
<div class="col-5 col-sm-4 fw-semibold text-muted">Priority:</div>
<div class="col-7 col-sm-8" id="lbl_priority"></div>
<!-- Summary panel --> <div class="col-5 col-sm-4 fw-semibold text-muted">Status:</div>
<div class="card mb-3"> <div class="col-7 col-sm-8" id="lbl_status"></div>
<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-5 col-sm-4 fw-semibold text-muted">Dispatch Code:</div>
<div class="col-7 col-sm-8" id="lbl_priority"></div> <div class="col-7 col-sm-8" id="lbl_dispatch_code"></div>
<div class="col-5 col-sm-4 fw-semibold">Status</div> <div class="col-5 col-sm-4 fw-semibold text-muted">Dispatch District:</div>
<div class="col-7 col-sm-8" id="lbl_status"></div> <div class="col-7 col-sm-8" id="lbl_dispatch_district"></div>
<div class="col-5 col-sm-4 fw-semibold">Dispatch Code</div> <div class="col-5 col-sm-4 fw-semibold text-muted">Address:</div>
<div class="col-7 col-sm-8" id="lbl_dispatch_code"></div> <div class="col-7 col-sm-8" id="lbl_address"></div>
<div class="col-5 col-sm-4 fw-semibold">Dispatch District</div>
<div class="col-7 col-sm-8" id="lbl_dispatch_district"></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>
<!-- 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>
</div> </div>
</div>
</div>
<!-- Details table (thead in HTML, tbody filled by TWebDBListControl) --> <!-- Buttons -->
<div class="card"> <div class="pb-2">
<div class="card-body p-0"> <div class="d-flex flex-wrap gap-2 justify-content-center">
<div class="table-responsive" style="max-height: 60vh; overflow-y: auto;"> <button type="button" class="btn btn-success btn-sm px-3 active" id="btn_remarks" aria-pressed="true">Remarks</button>
<table class="table table-sm table-striped mb-0"> <button type="button" class="btn btn-success btn-sm px-3" id="btn_history">History</button>
<thead class="table-light sticky-top"> <button type="button" class="btn btn-success btn-sm px-3" id="btn_warnings">Warnings</button>
<tr> <button type="button" class="btn btn-success btn-sm px-3" id="btn_contacts">Contacts</button>
<th style="width: 70px;">Type</th> </div>
<th style="width: 150px;">Timestamp</th>
<th>Remarks</th>
</tr>
</thead>
<tbody id="tbl_complaint_details"></tbody>
</table>
</div>
</div>
</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> </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="d-none"></div>
<div id="tbl_contacts" class="d-none"></div>
<div id="tbl_warnings" class="d-none"></div>
</div>
</div>
</div> </div>
</div> </div>
...@@ -4,14 +4,108 @@ interface ...@@ -4,14 +4,108 @@ interface
uses uses
System.SysUtils, System.Classes, JS, Web, System.SysUtils, System.Classes, JS, Web,
WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, WEBLib.Forms, WEBLib.Dialogs, WEBLib.Graphics,
WEBLib.Grids, WEBLib.DBCtrls; XData.Web.Client,
ConnectionModule,
Utils, Vcl.Controls, WEBLib.Controls, WEBLib.Grids, WEBLib.DBCtrls,
Data.DB, WEBLib.DB, XData.Web.JsonDataset, XData.Web.Dataset,
Vcl.StdCtrls, WEBLib.StdCtrls;
type type
TFViewComplaintDetails = class(TWebForm) TFViewComplaintDetails = class(TWebForm)
WebDBTableControl1: TWebDBTableControl; xdwcComplaintDetails: TXDataWebClient;
xdwdsRemarks: TXDataWebDataSet;
wdsRemarks: TWebDataSource;
xdwdsRemarksMemoType: TStringField;
xdwdsRemarksTimestamp: TStringField;
xdwdsRemarksRemarks: TStringField;
xdwdsRemarksBadgeNumber: TStringField;
xdwdsRemarksMemoId: TStringField;
xdwdsRemarksCFSId: TStringField;
xdwdsHistory: TXDataWebDataSet;
wdsHistory: TWebDataSource;
xdwdsHistoryApartment: TStringField;
xdwdsHistoryComplaint: TStringField;
xdwdsHistoryDateReported: TStringField;
xdwdsHistoryDPriority: TStringField;
xdwdsHistoryDCallType: TStringField;
xdwdsContacts: TXDataWebDataSet;
wdsContacts: TWebDataSource;
xdwdsContactsName: TStringField;
xdwdsContactsPhone: TStringField;
xdwdsContactsDContactType: TStringField;
xdwdsContactsRemarks: TStringField;
xdwdsWarnings: TXDataWebDataSet;
wdsWarnings: TWebDataSource;
xdwdsWarningsCodeDesc: TStringField;
xdwdsWarningsAddress: TStringField;
xdwdsWarningsNotes: TStringField;
tblRemarks: TWebDBTableControl;
tblContacts: TWebDBTableControl;
tblHistory: TWebDBTableControl;
tblWarnings: TWebDBTableControl;
btnRemarks: TWebButton;
btnHistory: TWebButton;
btnWarnings: TWebButton;
btnContacts: TWebButton;
btnCmp: TWebButton;
btnE911: TWebButton;
btnREM: TWebButton;
btnUnt: TWebButton;
procedure btnRemarksClick(Sender: TObject);
procedure btnHistoryClick(Sender: TObject);
procedure btnContactsClick(Sender: TObject);
procedure btnWarningsClick(Sender: TObject);
procedure btnCmpClick(Sender: TObject);
procedure btnE911Click(Sender: TObject);
procedure btnREMClick(Sender: TObject);
procedure btnUntClick(Sender: TObject);
private private
FComplaintId: string; FComplaintId: string;
FCfsId: string;
FLoading: Boolean;
FAllMemos: TJSArray;
FShowCmp: Boolean;
FShowE911: Boolean;
FShowRem: Boolean;
FShowUnt: Boolean;
FHasHistory: Boolean;
FHasContacts: Boolean;
FHasWarnings: Boolean;
FHistoryLoaded: Boolean;
FContactsLoaded: Boolean;
FWarningsLoaded: Boolean;
procedure WireUi;
procedure CloseClick(Event: TJSEvent);
procedure SetTextById(const elementId, value: string);
function MemoTypeInList(const memoType: string; const list: array of string): Boolean;
function MemoTypeLabel(const memoTypeCode: string): string;
function DeriveStatusFromDates(const dateDispatched, dateArrived, dateCleared: string): string;
procedure SetDataSetJsonData(dataSet: TXDataWebDataSet; dataArr: TJSArray);
procedure SetHiddenById(const elementId: string; hidden: Boolean);
procedure SetActiveTab(const tabName: string);
procedure UpdateToggleButtonCss(const buttonId: string; isOn: Boolean);
procedure UpdateTabButtonCss(const activeTabName: string);
procedure SetButtonEnabledById(const buttonId: string; enabled: Boolean);
procedure ApplyRemarksFilters;
[async] procedure ApplyTabAsync(const tabName: string);
[async] procedure LoadAsync;
[async] procedure LoadMemosAsync;
[async] procedure LoadHistoryAsync;
[async] procedure LoadContactsAsync;
[async] procedure LoadWarningsAsync;
public public
class function CreateForm(AElementID, ComplaintId: string): TWebForm; class function CreateForm(AElementID, ComplaintId: string): TWebForm;
procedure InitializeForm; procedure InitializeForm;
...@@ -22,6 +116,10 @@ var ...@@ -22,6 +116,10 @@ var
implementation implementation
uses
View.Main,
View.Complaints;
{$R *.dfm} {$R *.dfm}
class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string): TWebForm; class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string): TWebForm;
...@@ -31,7 +129,7 @@ class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string ...@@ -31,7 +129,7 @@ class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string
with TFViewComplaintDetails(AForm) do with TFViewComplaintDetails(AForm) do
begin begin
FComplaintId := ComplaintId; FComplaintId := ComplaintId;
InitializeForm; // kick off loading / UI binding here InitializeForm;
end; end;
end; end;
...@@ -41,153 +139,554 @@ end; ...@@ -41,153 +139,554 @@ end;
procedure TFViewComplaintDetails.InitializeForm; procedure TFViewComplaintDetails.InitializeForm;
begin begin
// TODO: xdwcComplaintDetails.Connection := DMConnection.ApiConnection;
// - call your XData endpoint with FComplaintId
// - bind fields / populate controls xdwdsRemarks.Connection := DMConnection.ApiConnection;
// - handle spinner / errors as you do elsewhere xdwdsHistory.Connection := DMConnection.ApiConnection;
xdwdsContacts.Connection := DMConnection.ApiConnection;
xdwdsWarnings.Connection := DMConnection.ApiConnection;
FShowCmp := True;
FShowE911 := True;
FShowRem := True;
FShowUnt := True;
FHasHistory := True;
FHasContacts := True;
FHasWarnings := True;
FHistoryLoaded := False;
FContactsLoaded := False;
FWarningsLoaded := False;
WireUi;
LoadAsync;
end; end;
end. procedure TFViewComplaintDetails.WireUi;
var
btnClose: TJSElement;
begin
btnClose := Document.getElementById('btn_close_complaint_details');
if btnClose <> nil then
btnClose.addEventListener('click', @CloseClick);
end;
procedure TFViewComplaintDetails.CloseClick(Event: TJSEvent);
begin
Event.preventDefault;
if Assigned(FViewMain) then
begin
FViewMain.SetActiveNavButton('view.main.btncomplaints');
FViewMain.ShowForm(TFViewComplaints);
end;
end;
procedure TFViewComplaintDetails.SetTextById(const elementId, value: string);
var
el: TJSElement;
begin
el := Document.getElementById(elementId);
if el <> nil then
TJSHtmlElement(el).innerText := value;
end;
function TFViewComplaintDetails.MemoTypeInList(const memoType: string; const list: array of string): Boolean;
var
i: Integer;
begin
Result := False;
for i := Low(list) to High(list) do
begin
if memoType = list[i] then
Exit(True);
end;
end;
function TFViewComplaintDetails.MemoTypeLabel(const memoTypeCode: string): string;
begin
if MemoTypeInList(memoTypeCode, ['30', '31', '32', '33', '34']) then
Exit('UNT');
if MemoTypeInList(memoTypeCode, ['3', '4']) then
Exit('CMP');
if MemoTypeInList(memoTypeCode, ['2']) then
Exit('E-911');
if MemoTypeInList(memoTypeCode, ['1']) then
Exit('REM');
Result := memoTypeCode;
end;
function TFViewComplaintDetails.DeriveStatusFromDates(const dateDispatched, dateArrived, dateCleared: string): string;
begin
if Trim(dateCleared) <> '' then
Exit('Cleared');
if Trim(dateArrived) <> '' then
Exit('On Scene');
if Trim(dateDispatched) <> '' then
Exit('Dispatched');
Result := 'Pending';
end;
procedure TFViewComplaintDetails.SetDataSetJsonData(dataSet: TXDataWebDataSet; dataArr: TJSArray);
begin
dataSet.Close;
dataSet.SetJsonData(dataArr);
dataSet.Open;
end;
procedure TFViewComplaintDetails.SetHiddenById(const elementId: string; hidden: Boolean);
var
el: TJSElement;
begin
el := Document.getElementById(elementId);
if el = nil then
Exit;
if hidden then
TJSElement(el).classList.add('d-none')
else
TJSElement(el).classList.remove('d-none');
end;
procedure TFViewComplaintDetails.UpdateToggleButtonCss(const buttonId: string; isOn: Boolean);
var
el: TJSElement;
btn: TJSHTMLButtonElement;
begin
el := Document.getElementById(buttonId);
if el = nil then
Exit;
btn := TJSHTMLButtonElement(el);
btn.classList.remove('btn-success');
btn.classList.remove('btn-secondary');
btn.classList.remove('active');
if isOn then
begin
btn.classList.add('btn-success');
btn.classList.add('active');
btn.setAttribute('aria-pressed', 'true');
end
else
begin
btn.classList.add('btn-secondary');
btn.setAttribute('aria-pressed', 'false');
end;
end;
procedure TFViewComplaintDetails.UpdateTabButtonCss(const activeTabName: string);
var
tabRemarks: TJSElement;
tabHistory: TJSElement;
tabContacts: TJSElement;
tabWarnings: TJSElement;
procedure SetTab(el: TJSElement; isActive: Boolean);
var
btn: TJSHTMLButtonElement;
begin
if el = nil then
Exit;
btn := TJSHTMLButtonElement(el);
btn.classList.remove('active');
if isActive then
begin
btn.classList.add('active');
btn.setAttribute('aria-pressed', 'true');
end
else
btn.setAttribute('aria-pressed', 'false');
end;
begin
tabRemarks := Document.getElementById('btn_remarks');
tabHistory := Document.getElementById('btn_history');
tabContacts := Document.getElementById('btn_contacts');
tabWarnings := Document.getElementById('btn_warnings');
SetTab(tabRemarks, activeTabName = 'remarks');
SetTab(tabHistory, activeTabName = 'history');
SetTab(tabContacts, activeTabName = 'contacts');
SetTab(tabWarnings, activeTabName = 'warnings');
end;
procedure TFViewComplaintDetails.SetButtonEnabledById(const buttonId: string; enabled: Boolean);
var
el: TJSElement;
btn: TJSHTMLButtonElement;
begin
el := Document.getElementById(buttonId);
if el = nil then
Exit;
btn := TJSHTMLButtonElement(el);
btn.disabled := not enabled;
btn.classList.remove('disabled');
btn.classList.remove('btn-success');
btn.classList.remove('btn-secondary');
if enabled then
begin
btn.classList.add('btn-success');
end
else
begin
btn.classList.add('btn-secondary');
btn.classList.add('disabled');
btn.classList.remove('active');
btn.setAttribute('aria-pressed', 'false');
end;
end;
procedure TFViewComplaintDetails.SetActiveTab(const tabName: string);
begin
SetHiddenById('tbl_remarks', tabName <> 'remarks');
SetHiddenById('tbl_history', tabName <> 'history');
SetHiddenById('tbl_contacts', tabName <> 'contacts');
SetHiddenById('tbl_warnings', tabName <> 'warnings');
SetHiddenById('row_remarks_toggles', tabName <> 'remarks');
UpdateTabButtonCss(tabName);
end;
procedure TFViewComplaintDetails.ApplyRemarksFilters;
var
filteredArr: TJSArray;
i: Integer;
rowObj: TJSObject;
memoType: string;
showRow: Boolean;
begin
if not Assigned(FAllMemos) then
Exit;
filteredArr := TJSArray.new;
for i := 0 to Integer(FAllMemos.length) - 1 do
begin
rowObj := TJSObject(FAllMemos[i]);
memoType := string(rowObj['MemoTypeCode']);
showRow := False;
if FShowCmp and MemoTypeInList(memoType, ['3', '4']) then
showRow := True;
if FShowE911 and MemoTypeInList(memoType, ['2']) then
showRow := True;
if FShowRem and MemoTypeInList(memoType, ['1']) then
showRow := True;
if FShowUnt and MemoTypeInList(memoType, ['30', '31', '32', '33', '34']) then
showRow := True;
if showRow then
filteredArr.push(rowObj);
end;
SetDataSetJsonData(xdwdsRemarks, filteredArr);
UpdateToggleButtonCss('btn_cmp', FShowCmp);
UpdateToggleButtonCss('btn_e911', FShowE911);
UpdateToggleButtonCss('btn_rem', FShowRem);
UpdateToggleButtonCss('btn_unt', FShowUnt);
end;
[async] procedure TFViewComplaintDetails.ApplyTabAsync(const tabName: string);
begin
if tabName = 'remarks' then
begin
SetActiveTab('remarks');
ApplyRemarksFilters;
Exit;
end;
if tabName = 'history' then
begin
if not FHasHistory then
Exit;
SetActiveTab('history');
if not FHistoryLoaded then
begin
ShowSpinner('spinner');
try
await(LoadHistoryAsync);
finally
HideSpinner('spinner');
end;
end;
Exit;
end;
if tabName = 'contacts' then
begin
if not FHasContacts then
Exit;
SetActiveTab('contacts');
if not FContactsLoaded then
begin
ShowSpinner('spinner');
try
await(LoadContactsAsync);
finally
HideSpinner('spinner');
end;
end;
Exit;
end;
if tabName = 'warnings' then
begin
if not FHasWarnings then
Exit;
SetActiveTab('warnings');
if not FWarningsLoaded then
begin
ShowSpinner('spinner');
try
await(LoadWarningsAsync);
finally
HideSpinner('spinner');
end;
end;
Exit;
end;
end;
procedure TFViewComplaintDetails.btnRemarksClick(Sender: TObject);
begin
ApplyTabAsync('remarks');
end;
procedure TFViewComplaintDetails.btnHistoryClick(Sender: TObject);
begin
ApplyTabAsync('history');
end;
procedure TFViewComplaintDetails.btnContactsClick(Sender: TObject);
begin
ApplyTabAsync('contacts');
end;
procedure TFViewComplaintDetails.btnWarningsClick(Sender: TObject);
begin
ApplyTabAsync('warnings');
end;
procedure TFViewComplaintDetails.btnCmpClick(Sender: TObject);
begin
FShowCmp := not FShowCmp;
ApplyRemarksFilters;
end;
procedure TFViewComplaintDetails.btnE911Click(Sender: TObject);
begin
FShowE911 := not FShowE911;
ApplyRemarksFilters;
end;
procedure TFViewComplaintDetails.btnREMClick(Sender: TObject);
begin
FShowRem := not FShowRem;
ApplyRemarksFilters;
end;
procedure TFViewComplaintDetails.btnUntClick(Sender: TObject);
begin
FShowUnt := not FShowUnt;
ApplyRemarksFilters;
end;
[async] procedure TFViewComplaintDetails.LoadAsync;
var
resp: TXDataClientResponse;
rootObj: TJSObject;
dataObj: TJSObject;
complaintText: string;
priorityText: string;
statusText: string;
dispatchDescText: string;
dispatchDistrictText: string;
addressText: string;
dateDispatchedText: string;
dateArrivedText: string;
dateClearedText: string;
historyFlag: string;
contactsFlag: string;
warningsFlag: string;
begin
if FLoading then
Exit;
FLoading := True;
ShowSpinner('spinner');
try
try
resp := await(xdwcComplaintDetails.RawInvokeAsync('IApiService.GetComplaintDetails', [FComplaintId]));
rootObj := TJSObject(resp.Result);
dataObj := TJSObject(rootObj['data']);
complaintText := string(dataObj['Complaint']);
priorityText := string(dataObj['Priority']);
dispatchDescText := string(dataObj['DispatchCodeDesc']);
dispatchDistrictText := string(dataObj['DispatchDistrict']);
addressText := string(dataObj['Address']);
FCfsId := string(dataObj['CFSId']);
statusText := '';
if dataObj['Status'] <> nil then
statusText := string(dataObj['Status']);
dateDispatchedText := '';
dateArrivedText := '';
dateClearedText := '';
if dataObj['DateDispatched'] <> nil then
dateDispatchedText := string(dataObj['DateDispatched']);
if dataObj['DateArrived'] <> nil then
dateArrivedText := string(dataObj['DateArrived']);
if dataObj['DateCleared'] <> nil then
dateClearedText := string(dataObj['DateCleared']);
if Trim(statusText) = '' then
statusText := DeriveStatusFromDates(dateDispatchedText, dateArrivedText, dateClearedText);
// Note: reenable if using the complaint in list html that is currently commented out
// SetTextById('lbl_complaint_number', complaintText);
SetTextById('lbl_summary_title', 'Summary for Complaint ' + complaintText);
SetTextById('lbl_priority', priorityText);
SetTextById('lbl_status', statusText);
SetTextById('lbl_dispatch_code', dispatchDescText);
SetTextById('lbl_dispatch_district', dispatchDistrictText);
SetTextById('lbl_address', addressText);
historyFlag := '';
contactsFlag := '';
warningsFlag := '';
if dataObj['History'] <> nil then
historyFlag := string(dataObj['History']);
if dataObj['Contacts'] <> nil then
contactsFlag := string(dataObj['Contacts']);
if dataObj['Warnings'] <> nil then
warningsFlag := string(dataObj['Warnings']);
FHasHistory := historyFlag <> '-1';
FHasContacts := contactsFlag <> '-1';
FHasWarnings := warningsFlag <> '-1';
SetButtonEnabledById('btn_history', FHasHistory);
SetButtonEnabledById('btn_contacts', FHasContacts);
SetButtonEnabledById('btn_warnings', FHasWarnings);
await(LoadMemosAsync);
SetActiveTab('remarks');
ApplyRemarksFilters;
except
on E: EXDataClientRequestException do
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
on E: Exception do
Utils.ShowErrorModal(E.Message);
end;
finally
HideSpinner('spinner');
FLoading := False;
end;
end;
[async] procedure TFViewComplaintDetails.LoadMemosAsync;
var
resp: TXDataClientResponse;
rootObj: TJSObject;
dataArr: TJSArray;
i: Integer;
rowObj: TJSObject;
memoTypeCode: string;
begin
if Trim(FCfsId) = '' then
Exit;
//unit View.ComplaintDetails; resp := await(xdwcComplaintDetails.RawInvokeAsync('IApiService.GetComplaintMemos', [FCfsId]));
// rootObj := TJSObject(resp.Result);
//interface
// dataArr := TJSArray(rootObj['data']);
//uses FAllMemos := dataArr;
// System.SysUtils, System.Classes, JS, Web,
// WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs, for i := 0 to Integer(dataArr.length) - 1 do
// WEBLib.StdCtrls, WEBLib.WebCtrls, WEBLib.ExtCtrls, begin
// WEBLib.DB, Data.DB, rowObj := TJSObject(dataArr[i]);
// XData.Web.Client, XData.Web.JsonDataset, XData.Web.Dataset, memoTypeCode := string(rowObj['MemoType']);
// View.Main, View.Complaints, Vcl.Controls, WEBLib.Grids, WEBLib.DBCtrls; rowObj['MemoTypeCode'] := memoTypeCode;
// rowObj['MemoType'] := MemoTypeLabel(memoTypeCode);
//type end;
// TFViewComplaintDetails = class(TWebForm)
// // Header controls (ElementID -> HTML ids in snake_case) SetDataSetJsonData(xdwdsRemarks, dataArr);
// btnCloseComplaintDetails: TWebButton; // ElementID = 'btn_close_complaint_details' end;
// lblComplaintNumber: TWebLabel; // 'lbl_complaint_number'
// lblPriority: TWebLabel; // 'lbl_priority' [async] procedure TFViewComplaintDetails.LoadHistoryAsync;
// lblStatus: TWebLabel; // 'lbl_status' var
// lblDispatchCode: TWebLabel; // 'lbl_dispatch_code' resp: TXDataClientResponse;
// lblDispatchDistrict: TWebLabel; // 'lbl_dispatch_district' rootObj: TJSObject;
// lblAddress: TWebLabel; // 'lbl_address' dataArr: TJSArray;
// begin
// // Action buttons (optional events later) resp := await(xdwcComplaintDetails.RawInvokeAsync('IApiService.GetComplaintHistory', [FComplaintId]));
// btnHistory: TWebButton; // 'btn_history' rootObj := TJSObject(resp.Result);
// btnWarnings: TWebButton; // 'btn_warnings'
// btnContacts: TWebButton; // 'btn_contacts' dataArr := TJSArray(rootObj['data']);
// btnCMP: TWebButton; // 'btn_cmp' SetDataSetJsonData(xdwdsHistory, dataArr);
// btnE911: TWebButton; // 'btn_e911'
// btnREM: TWebButton; // 'btn_rem' FHistoryLoaded := True;
// btnUNT: TWebButton; // 'btn_unt' end;
//
// // Data-aware table [async] procedure TFViewComplaintDetails.LoadContactsAsync;
// tblComplaintDetails: TWebDBListControl; // ElementID = 'tbl_complaint_details' var
// xdwcComplaintDetails: TXDataWebClient; resp: TXDataClientResponse;
// xdwdsxComplaintDetails: TXDataWebDataSet; // (your requested name) rootObj: TJSObject;
// wdsComplaintDetails: TWebDataSource; dataArr: TJSArray;
// begin
// private resp := await(xdwcComplaintDetails.RawInvokeAsync('IApiService.GetComplaintContacts', [FComplaintId]));
// FComplaintId: string; rootObj := TJSObject(resp.Result);
//
// [async] procedure LoadHeaderAsync; dataArr := TJSArray(rootObj['data']);
// [async] procedure LoadRowsAsync; SetDataSetJsonData(xdwdsContacts, dataArr);
//
// public FContactsLoaded := True;
// class function CreateForm(AElementID, ComplaintId: string): TWebForm; end;
// procedure InitializeForm;
// end; [async] procedure TFViewComplaintDetails.LoadWarningsAsync;
// var
//implementation resp: TXDataClientResponse;
// rootObj: TJSObject;
//{$R *.dfm} dataArr: TJSArray;
// begin
//class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string): TWebForm; resp := await(xdwcComplaintDetails.RawInvokeAsync('IApiService.GetComplaintWarnings', [FComplaintId]));
// rootObj := TJSObject(resp.Result);
// procedure AfterCreate(AForm: TObject);
// begin dataArr := TJSArray(rootObj['data']);
// with TFViewComplaintDetails(AForm) do SetDataSetJsonData(xdwdsWarnings, dataArr);
// begin
// FComplaintId := ComplaintId; FWarningsLoaded := True;
// InitializeForm; end;
// end;
// end; end.
//
//begin
// Application.CreateForm(TFViewComplaintDetails, AElementID, Result, @AfterCreate);
//end;
//
//procedure TFViewComplaintDetails.InitializeForm;
//begin
// // Close button navigates back via Main (keeps host container ownership clean)
// btnCloseComplaintDetails.OnClick :=
// procedure(Sender: TObject)
// begin
// if Assigned(FViewMain) then
// begin
//// FViewMain.SetActiveNavButton('view.main.btncomplaints');
// FViewMain.ShowForm(TFViewComplaints);
// end;
// end;
//
// // Data-aware wiring (design-time also OK; keeping here for clarity)
// wdsComplaintDetails.DataSet := xdwdsxComplaintDetails;
// tblComplaintDetails.DataSource := wdsComplaintDetails;
//
// // Expecting ItemTemplate at design-time:
// // <tr><td>(%MemoType%)</td><td>(%Timestamp%)</td><td>(%Remarks%)</td></tr>
//
// // Kick off loads
// LoadHeaderAsync;
// LoadRowsAsync;
//end;
//
//[async] procedure TFViewComplaintDetails.LoadHeaderAsync;
//var
// resp: TXDataClientResponse;
// obj: TJSObject;
//begin
// try
// // TODO:Adjust endpoint and args to match your server (array param is common in XData)
// resp := await(xdwcComplaintDetails.RawInvokeAsync('IApiService.GetComplaintDetailsHeader', [FComplaintId]));
// obj := TJSObject(resp.Result);
//
// // If the server returns { data: {...} }, unwrap:
// if obj['data'] <> nil then
// obj := TJSObject(obj['data']);
//
// lblComplaintNumber.Caption := string(obj['ComplaintNumber'] ?? FComplaintId);
// lblPriority.Caption := string(obj['Priority'] ?? '');
// lblStatus.Caption := string(obj['Status'] ?? '');
// lblDispatchCode.Caption := string(obj['DispatchCodeDesc'] ?? '');
// lblDispatchDistrict.Caption := string(obj['DispatchDistrict'] ?? '');
// lblAddress.Caption := string(obj['Address'] ?? '');
// except
// on E: Exception do
// Console.Log('Header load error: ' + E.Message);
// end;
//end;
//
//[async] procedure TFViewComplaintDetails.LoadRowsAsync;
//var
// resp: TXDataClientResponse;
// root: TJSObject;
//begin
// try
// // Adjust endpoint name/params to your API
// resp := await(xdwcComplaintDetails.RawInvokeAsync('IApiService.GetComplaintDetailsRows', [FComplaintId]));
// root := TJSObject(resp.Result);
//
// // Expecting { data: [ { MemoType, Timestamp, Remarks }, ... ] }
// xdwdsxComplaintDetails.Close;
// xdwdsxComplaintDetails.SetJsonData(root['data']);
// xdwdsxComplaintDetails.Open;
// except
// on E: Exception do
// Console.Log('Rows load error: ' + E.Message);
// end;
//end;
//
//end.
<div class="d-flex flex-column vh-100"> <div class="d-flex flex-column vh-100">
<!-- Top Nav --> <!-- 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"> <div class="container-fluid">
<!-- Left: Font button --> <!-- Left: Font button -->
<button id="view.main.btnfont" type="button" class="btn btn-outline-primary text-light border-light btn-sm"> <button id="view.main.btnfont" type="button" class="btn btn-outline-primary text-light border-light btn-sm">
...@@ -12,17 +12,17 @@ ...@@ -12,17 +12,17 @@
<a id="view.main.apptitle" class="navbar-brand text-light ms-3" href="index.html">emiMobile</a> <a id="view.main.apptitle" class="navbar-brand text-light ms-3" href="index.html">emiMobile</a>
<!-- Right: Connection label --> <!-- Right: Connection label -->
<span id="view.main.lblconnection" class="navbar-text text-light ms-auto">Connected</span> <span id="view.main.lblconnection" class="navbar-text text-light ms-auto"></span>
</div> </div>
</nav> </nav>
<!-- Main content: fills space between navbars --> <!-- 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 --> <!-- TWebPanel content gets injected here -->
</main> </main>
<!-- Bottom Nav --> <!-- 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="container-fluid">
<div class="d-flex justify-content-center gap-3 w-100"> <div class="d-flex justify-content-center gap-3 w-100">
<button id="view.main.btnmap" type="button" class="btn btn-primary"> <button id="view.main.btnmap" type="button" class="btn btn-primary">
......
...@@ -44,8 +44,9 @@ type ...@@ -44,8 +44,9 @@ type
procedure ShowCrudForm( AFormClass: TWebFormClass ); procedure ShowCrudForm( AFormClass: TWebFormClass );
//procedure EditUser( AParam, BParam, CParam, DParam, EParam: string); //procedure EditUser( AParam, BParam, CParam, DParam, EParam: string);
function GetUserInfo: string; function GetUserInfo: string;
procedure SetActiveNavButton(const BtnId: string);
[async] procedure RefreshBadgesAsync; [async] procedure RefreshBadgesAsync;
procedure ShowUnitDetails(UnitId: string);
procedure SetHeaderTitle(const title: string);
public public
{ Public declarations } { Public declarations }
class procedure Display(LogoutProc: TLogoutProc); class procedure Display(LogoutProc: TLogoutProc);
...@@ -53,6 +54,7 @@ type ...@@ -53,6 +54,7 @@ type
procedure EditUser( Mode, FullName, Username, Phone, Email: string; admin, active: boolean); procedure EditUser( Mode, FullName, Username, Phone, Email: string; admin, active: boolean);
procedure ShowUserForm(Info: string); procedure ShowUserForm(Info: string);
procedure ShowComplaintDetails(ComplaintId: string); procedure ShowComplaintDetails(ComplaintId: string);
procedure SetActiveNavButton(const BtnId: string);
end; end;
var var
...@@ -71,6 +73,7 @@ uses ...@@ -71,6 +73,7 @@ uses
View.Admin, View.Admin,
View.Users, View.Users,
View.EditUser, View.EditUser,
View.UnitDetails,
Utils; Utils;
{$R *.dfm} {$R *.dfm}
...@@ -90,6 +93,15 @@ begin ...@@ -90,6 +93,15 @@ begin
RefreshBadgesAsync; RefreshBadgesAsync;
end; end;
procedure TFViewMain.SetHeaderTitle(const title: string);
var
el: TJSElement;
begin
el := Document.getElementById('view.main.lbltitle');
if el <> nil then
TJSHtmlElement(el).innerText := title;
end;
procedure TFViewMain.lblUsersClick(Sender: TObject); procedure TFViewMain.lblUsersClick(Sender: TObject);
begin begin
...@@ -136,6 +148,7 @@ end; ...@@ -136,6 +148,7 @@ end;
procedure TFViewMain.btnComplaintsClick(Sender: TObject); procedure TFViewMain.btnComplaintsClick(Sender: TObject);
begin begin
SetHeaderTitle('Complaints');
SetActiveNavButton('view.main.btncomplaints'); SetActiveNavButton('view.main.btncomplaints');
ShowForm(TFViewComplaints); ShowForm(TFViewComplaints);
...@@ -149,12 +162,14 @@ end; ...@@ -149,12 +162,14 @@ end;
procedure TFViewMain.btnMapClick(Sender: TObject); procedure TFViewMain.btnMapClick(Sender: TObject);
begin begin
SetHeaderTitle('Map');
SetActiveNavButton('view.main.btnmap'); SetActiveNavButton('view.main.btnmap');
ShowForm(TFViewMap); ShowForm(TFViewMap);
end; end;
procedure TFViewMain.btnUnitsClick(Sender: TObject); procedure TFViewMain.btnUnitsClick(Sender: TObject);
begin begin
SetHeaderTitle('Units');
SetActiveNavButton('view.main.btnunits'); SetActiveNavButton('view.main.btnunits');
ShowForm(TFViewUnits); ShowForm(TFViewUnits);
end; end;
...@@ -197,11 +212,19 @@ end; ...@@ -197,11 +212,19 @@ end;
procedure TFViewMain.ShowComplaintDetails(ComplaintId: string); procedure TFViewMain.ShowComplaintDetails(ComplaintId: string);
begin begin
SetHeaderTitle('Complaint Details');
if Assigned(FChildForm) then if Assigned(FChildForm) then
FChildForm.Free; FChildForm.Free;
FChildForm := TFViewComplaintDetails.CreateForm(WebPanel1.ElementID, ComplaintId); FChildForm := TFViewComplaintDetails.CreateForm(WebPanel1.ElementID, ComplaintId);
end; 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); procedure TFViewMain.SetActiveNavButton(const btnId: string);
......
...@@ -74,8 +74,8 @@ object FViewMap: TFViewMap ...@@ -74,8 +74,8 @@ object FViewMap: TFViewMap
Width = 335 Width = 335
Height = 555 Height = 555
ElementID = 'map_pnlmap' ElementID = 'map_pnlmap'
Caption = 'pnlMap'
ChildOrder = 7 ChildOrder = 7
ElementPosition = epIgnore
TabOrder = 6 TabOrder = 6
object lfMap: TTMSFNCLeaflet object lfMap: TTMSFNCLeaflet
Left = 0 Left = 0
...@@ -131,4 +131,10 @@ object FViewMap: TFViewMap ...@@ -131,4 +131,10 @@ object FViewMap: TFViewMap
Left = 358 Left = 358
Top = 696 Top = 696
end end
object tmrZoomToBounds: TWebTimer
Enabled = False
OnTimer = tmrZoomToBoundsTimer
Left = 358
Top = 750
end
end end
<!-- Root wrapper inside main.webpanel --> <div id="map.root" class="d-flex flex-column h-100 w-100">
<div id="map.root" class="d-flex flex-column" style="height:100%;">
<nav class="d-flex gap-2 bg-primary p-2 overflow-x-auto border-bottom border-primary shadow-sm flex-shrink-0">
<!-- Local navbar -->
<nav class="navbar navbar-dark bg-primary py-2"> <button id="map.btnmenu" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<div class="container-fluid"> <i class="fa fa-bars me-2"></i><span class="d-none d-sm-inline">Menu</span>
<div class="row w-100 g-2 align-items-stretch"> </button>
<div class="col">
<button id="map.btnmenu" type="button" class="btn btn-primary w-100 h-100"> <button id="map.btnalerts" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<i class="fa fa-bars me-1"></i><span class="d-none d-sm-inline">Menu</span> <i class="fa fa-exclamation-circle me-2"></i><span class="d-none d-sm-inline">Alerts</span>
</button> </button>
</div>
<div class="col"> <button id="map.btngroups" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<button id="map.btnalerts" type="button" class="btn btn-primary w-100 h-100"> <i class="fa fa-users me-2"></i><span class="d-none d-sm-inline">Groups</span>
<i class="fa fa-exclamation-circle me-1"></i><span class="d-none d-sm-inline">Alerts</span> </button>
</button>
</div> <button id="map.btnlocate" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
<div class="col"> <i class="fa fa-location-arrow me-2"></i><span class="d-none d-sm-inline">Locate</span>
<button id="map.btngroups" type="button" class="btn btn-primary w-100 h-100"> </button>
<i class="fa fa-users me-1"></i><span class="d-none d-sm-inline">Groups</span>
</button> <button id="map.btnfilters" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
</div> <i class="fa fa-sliders-h me-2"></i><span class="d-none d-sm-inline">Filter</span>
<div class="col"> </button>
<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 id="map.btndisplay" type="button" class="btn btn-primary d-flex align-items-center justify-content-center flex-fill">
</button> <i class="fa fa-sun me-2"></i><span class="d-none d-sm-inline">Display</span>
</div> </button>
<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>
<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>
</div>
</div>
</nav> </nav>
<!-- Map fills remaining space --> <div class="flex-grow-1 position-relative" style="min-height: 0;">
<div class="flex-grow-1" style="min-height:400px;"> <div id="map_pnlmap" class="position-absolute w-100 h-100 top-0 start-0"></div>
<!-- TWebPanel (pnlMap) will render itself here -->
<div id="map_pnlmap" class="w-100 h-100"></div>
</div> </div>
</div> </div>
......
...@@ -23,18 +23,20 @@ type ...@@ -23,18 +23,20 @@ type
httpReqGeoJson: TWebHttpRequest; httpReqGeoJson: TWebHttpRequest;
xdwcMap: TXDataWebClient; xdwcMap: TXDataWebClient;
tmrRefresh: TWebTimer; tmrRefresh: TWebTimer;
tmrZoomToBounds: TWebTimer;
procedure lfMapMapInitialized(Sender: TObject); procedure lfMapMapInitialized(Sender: TObject);
[async] procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string); [async] procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
procedure lfMapCustomizeMarker(Sender: TObject; procedure lfMapCustomizeMarker(Sender: TObject;
var ACustomizeMarker: string); var ACustomizeMarker: string);
procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string); procedure lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
procedure tmrZoomToBoundsTimer(Sender: TObject);
private private
FUnitsLoaded: Boolean; FUnitsLoaded: Boolean;
FComplaintsLoaded: Boolean; FComplaintsLoaded: Boolean;
FZoomPending: Boolean;
[async] procedure LoadPointsAsync; [async] procedure LoadPointsAsync;
function CarIconForDistrict(const DistrictCode: string): string; function CarIconForDistrict(const DistrictCode: string): string;
procedure ShowUnitDetails(const unitId: string);
public public
end; end;
...@@ -60,7 +62,6 @@ begin ...@@ -60,7 +62,6 @@ begin
console.log('JS bridge showComplaintDetails called, id=', id); console.log('JS bridge showComplaintDetails called, id=', id);
try { try {
pas['View.Main'].FViewMain.ShowComplaintDetails(id); pas['View.Main'].FViewMain.ShowComplaintDetails(id);
console.log('TFViewMain.ShowComplaintDetails finished OK');
} catch (e) { } catch (e) {
console.log('Error in TFViewMain.ShowComplaintDetails', e); console.log('Error in TFViewMain.ShowComplaintDetails', e);
} }
...@@ -69,20 +70,15 @@ begin ...@@ -69,20 +70,15 @@ begin
window.showUnitDetails = function (id) { window.showUnitDetails = function (id) {
console.log('JS bridge showUnitDetails called, id=', id); console.log('JS bridge showUnitDetails called, id=', id);
try { try {
pas['View.Map'].FViewMap.ShowUnitDetails(id); pas['View.Main'].FViewMain.ShowUnitDetails(id);
} catch (e) { } catch (e) {
console.log('Error in TFViewMap.ShowUnitDetails', e); console.log('Error in TFViewMain.ShowUnitDetails', e);
} }
}; };
end; end;
{$ENDIF} {$ENDIF}
end; end;
procedure TFViewMap.ShowUnitDetails(const unitId: string);
begin
ShowMessage('Link to Unit Details form');
end;
[async] procedure TFViewMap.httpReqGeoJsonResponse(Sender: TObject; AResponse: string); [async] procedure TFViewMap.httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
var var
...@@ -137,7 +133,10 @@ begin ...@@ -137,7 +133,10 @@ begin
end; end;
if lfMap.Polygons.Count > 0 then if lfMap.Polygons.Count > 0 then
lfMap.ZoomToBounds(lfMap.Polygons.ToCoordinateArray); begin
FZoomPending := True;
tmrZoomToBounds.Enabled := True;
end;
finally finally
lfMap.EndUpdate; lfMap.EndUpdate;
end; end;
...@@ -181,7 +180,6 @@ begin ...@@ -181,7 +180,6 @@ begin
ShowSpinner('spinner'); ShowSpinner('spinner');
FUnitsLoaded := False; FUnitsLoaded := False;
FComplaintsLoaded := False; FComplaintsLoaded := False;
// --- Units --------------------------------------------------------------- // --- Units ---------------------------------------------------------------
try try
resp := await(xdwcMap.RawInvokeAsync('IApiService.GetUnitMap', [])); resp := await(xdwcMap.RawInvokeAsync('IApiService.GetUnitMap', []));
...@@ -376,7 +374,6 @@ begin ...@@ -376,7 +374,6 @@ begin
'var t = o.tooltipHtml || "";' + #13#10 + 'var t = o.tooltipHtml || "";' + #13#10 +
'var u = (o.icon && o.icon.options && o.icon.options.iconUrl) ? o.icon.options.iconUrl : null;' + #13#10 + 'var u = (o.icon && o.icon.options && o.icon.options.iconUrl) ? o.icon.options.iconUrl : null;' + #13#10 +
// clear any old tooltip/popup bindings
'try { if (m.unbindTooltip) m.unbindTooltip(); } catch(e) {}' + #13#10 + 'try { if (m.unbindTooltip) m.unbindTooltip(); } catch(e) {}' + #13#10 +
'try { if (m.unbindPopup) m.unbindPopup(); } catch(e) {}' + #13#10 + 'try { if (m.unbindPopup) m.unbindPopup(); } catch(e) {}' + #13#10 +
...@@ -431,28 +428,25 @@ end; ...@@ -431,28 +428,25 @@ end;
procedure TFViewMap.lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string); procedure TFViewMap.lfMapCustomizeCSS(Sender: TObject; var ACustomizeCSS: string);
begin begin
ACustomizeCSS := ACustomizeCSS :=
// popup container: rounded, shadow // --- Popup container----------------------------------------------------------
'.emi-tip .leaflet-popup-content-wrapper{' + '.emi-tip .leaflet-popup-content-wrapper{' +
'border-radius:8px;' + 'border-radius:8px;' +
'box-shadow:0 4px 14px rgba(0,0,0,.25);' + 'box-shadow:0 4px 14px rgba(0,0,0,.25);' +
'}' + #13#10 + '}' + #13#10 +
// --- Popup content------------------------------------------------------------
// popup content: no fixed width, max 260px on normal screens
'.emi-tip .leaflet-popup-content{' + '.emi-tip .leaflet-popup-content{' +
'margin:0;' + 'margin:0;' +
'padding:0;' + 'padding:0;' +
'width:auto;' + 'width:auto;' +
'max-width:260px;' + 'max-width:260px;' +
'}' + #13#10 + '}' + #13#10 +
// --- Media Query: on very small screens, lets it grow almost full width ------
// on very small screens, let it grow almost full width
'@media (max-width:480px){' + '@media (max-width:480px){' +
'.emi-tip .leaflet-popup-content{' + '.emi-tip .leaflet-popup-content{' +
'max-width:calc(100vw - 32px);' + 'max-width:calc(100vw - 32px);' +
'}' + '}' +
'}' + #13#10 + '}' + #13#10 +
// --- Table: compact, wraps text ----------------------------------------------
// table: compact, wraps text
'.emi-tip .emi-tip-table{display:table;border-collapse:collapse;table-layout:auto;width:auto;margin:.25rem 0;}'+#13#10+ '.emi-tip .emi-tip-table{display:table;border-collapse:collapse;table-layout:auto;width:auto;margin:.25rem 0;}'+#13#10+
'.emi-tip .emi-tip-table thead{display:table-header-group;}'+#13#10+ '.emi-tip .emi-tip-table thead{display:table-header-group;}'+#13#10+
'.emi-tip .emi-tip-table tbody{display:table-row-group;}'+#13#10+ '.emi-tip .emi-tip-table tbody{display:table-row-group;}'+#13#10+
...@@ -465,13 +459,27 @@ begin ...@@ -465,13 +459,27 @@ begin
'font-size:11px;' + 'font-size:11px;' +
'vertical-align:middle;' + 'vertical-align:middle;' +
'}' + #13#10 + '}' + #13#10 +
// --- Marker Badge ------------------------------------------------------------
// marker badge
'.emi-marker-wrap{position:relative;display:inline-block;}'+#13#10+ '.emi-marker-wrap{position:relative;display:inline-block;}'+#13#10+
'.emi-marker-img{display:block;}'+#13#10+ '.emi-marker-img{display:block;}'+#13#10+
'.emi-marker-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--bs-danger);color:#fff;font:700 11px/16px system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;box-shadow:0 0 0 2px #fff;}'; '.emi-marker-badge{position:absolute;top:-4px;right:-4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--bs-danger);color:#fff;font:700 11px/16px system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;box-shadow:0 0 0 2px #fff;}';
end; end;
procedure TFViewMap.tmrZoomToBoundsTimer(Sender: TObject);
begin
tmrZoomToBounds.Enabled := False;
if not FZoomPending then
Exit;
FZoomPending := False;
if lfMap.Polygons.Count = 0 then
Exit;
// Note: Delaying avoids Leaflet moveend firing while the internal map is still null.
lfMap.ZoomToBounds(lfMap.Polygons.ToCoordinateArray);
end;
end. end.
object FViewUnitDetails: TFViewUnitDetails
Width = 640
Height = 480
end
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>TMS Web Project</title>
<style>
</style>
</head>
<body>
</body>
</html>
\ No newline at end of file
unit View.UnitDetails;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs;
type
TFViewUnitDetails = class(TWebForm)
private
FUnitId: string;
procedure InitializeForm;
public
class function CreateForm(AElementID, UnitId: string): TWebForm;
end;
var
FViewUnitDetails: TFViewUnitDetails;
implementation
{$R *.dfm}
class function TFViewUnitDetails.CreateForm(AElementID, UnitId: string): TWebForm;
procedure AfterCreate(AForm: TObject);
begin
with TFViewUnitDetails(AForm) do
begin
FUnitId := UnitId;
InitializeForm; // kick off loading / UI binding here
end;
end;
begin
Application.CreateForm(TFViewUnitDetails, AElementID, Result, @AfterCreate);
end;
procedure TFViewUnitDetails.InitializeForm;
begin
// TODO:
// - call your XData endpoint with FUnitId
// - bind fields / populate controls
// - handle spinner / errors as you do elsewhere
end;
end.
\ No newline at end of file
/* --- TMS WEB Core Specific Fixes --- */
/* Removes the default border from the main Form wrapper */
span.card {
border: none;
}
/* --- Login Screen Styling --- */
.login-card { .login-card {
display: inline-block; display: inline-block; /* Or use d-flex on the parent to center it */
width: 300px; /* Adjust width as needed */ width: 100%;
max-width: 350px; /* Better than fixed 300px */
padding: 0; padding: 0;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff; background-color: #fff;
} }
.mr-2 { /* Optional: Custom navbar look for the login screen */
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
}
.login-navbar { .login-navbar {
max-width: 1200px; /* Set the max-width to match a medium screen */ max-width: 1200px;
margin: auto; margin: auto;
border-bottom-left-radius: 10px; /* Round the bottom left corner */ border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px; /* Round the bottom right corner */ border-bottom-right-radius: 10px;
border: 1px solid #d3d3d3; border: 1px solid #d3d3d3;
} }
.navbar-toggler { /* --- Table Customization --- */
display: none; /* Bootstrap has .table-hover, but this sets your specific blue color */
} .table tbody tr:hover {
background-color: #d1e7fd;
.dropdown-menu a { cursor: pointer;
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 */ /* --- Pagination Theme Overrides --- */
.selected-number .page-link { /* These override Bootstrap's blue to your specific darker blue (#204d74) */
background-color: #204d74;
color: #fff !important;
}
/* Style for the unselected numbers and text (previous/next) */ .pagination .page-link {
.pagination .page-item a, color: #204d74; /* Text color for standard links */
.pagination .page-item span {
color: #204d74;
} }
.pagination .page-item.active .page-link, /* Active State (Selected Page) */
.pagination .page-item.active .page-link:hover, .pagination .page-item.active .page-link {
.pagination .page-item.active .page-link:focus {
background-color: #204d74; background-color: #204d74;
border-color: #204d74; border-color: #204d74;
color: #fff !important; color: #fff !important;
} }
/* This is needed to get rid of the line that was appearing. */ /* Hover State */
span.card { .pagination .page-item:not(.active) .page-link:hover {
border: none; background-color: #e9ecef; /* Standard Bootstrap light grey hover */
color: #16344a; /* Darker text on hover */
} }
/* --- Utilities --- */
.modal-backdrop { .list-section-header:empty {
z-index: 1040 !important; display: none;
}
.modal {
z-index: 1055 !important;
} }
#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;
}
...@@ -21,7 +21,8 @@ uses ...@@ -21,7 +21,8 @@ uses
View.EditUser in 'View.EditUser.pas' {FViewEditUser: TWebForm} {*.html}, View.EditUser in 'View.EditUser.pas' {FViewEditUser: TWebForm} {*.html},
Utils in 'Utils.pas', Utils in 'Utils.pas',
View.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html}, 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};
{$R *.res} {$R *.res}
......
...@@ -181,6 +181,11 @@ ...@@ -181,6 +181,11 @@
<FormType>dfm</FormType> <FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="View.UnitDetails.pas">
<Form>FViewUnitDetails</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<None Include="index.html"/> <None Include="index.html"/>
<None Include="css\app.css"/> <None Include="css\app.css"/>
<None Include="css\spinner.css"/> <None Include="css\spinner.css"/>
......
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