Commit f952c2be by Mac Stephens

Switched complaint map to Leaflet popups, updated the popups to match current…

Switched complaint map to Leaflet popups, updated the popups to match current app. Fixed details click wiring, and tightened popup/list card layout & CSS.
parent 1f94b0a6
object ApiDatabaseModule: TApiDatabaseModule object ApiDatabaseModule: TApiDatabaseModule
Height = 491 Height = 491
Width = 640 Width = 640
object ucEnvoy: TUniConnection
ProviderName = 'PostgreSQL'
SpecificOptions.Strings = (
'PostgreSQL.Schema=envoy')
LoginPrompt = False
Left = 35
Top = 29
end
object PostgreSQLUniProvider1: TPostgreSQLUniProvider
Left = 180
Top = 30
end
object UniQuery1: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'')
Left = 351
Top = 34
end
object OracleUniProvider1: TOracleUniProvider object OracleUniProvider1: TOracleUniProvider
Left = 182 Left = 182
Top = 98 Top = 98
...@@ -632,7 +613,8 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -632,7 +613,8 @@ object ApiDatabaseModule: TApiDatabaseModule
' ).sdo_point.y' ' ).sdo_point.y'
' END AS LAT,' ' END AS LAT,'
'' ''
' cdc.CODE_DESC AS DISPATCH_CODE_DESC' ' cdc.CODE_DESC AS DISPATCH_CODE_DESC,'
' ca.ADDRESS AS ADDRESS'
'FROM COMPLAINT_ACTIVE ca' 'FROM COMPLAINT_ACTIVE ca'
'JOIN COMPLAINT_TIMES ct' 'JOIN COMPLAINT_TIMES ct'
' ON ct.COMPLAINTID = ca.COMPLAINTID' ' ON ct.COMPLAINTID = ca.COMPLAINTID'
...@@ -675,6 +657,10 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -675,6 +657,10 @@ object ApiDatabaseModule: TApiDatabaseModule
ReadOnly = True ReadOnly = True
Size = 50 Size = 50
end end
object uqMapComplaintsADDRESS: TStringField
FieldName = 'ADDRESS'
Size = 64
end
object uqMapComplaintspriorityKey: TStringField object uqMapComplaintspriorityKey: TStringField
FieldKind = fkCalculated FieldKind = fkCalculated
FieldName = 'priorityKey' FieldName = 'priorityKey'
...@@ -711,4 +697,54 @@ object ApiDatabaseModule: TApiDatabaseModule ...@@ -711,4 +697,54 @@ object ApiDatabaseModule: TApiDatabaseModule
ReadOnly = True ReadOnly = True
end end
end end
object uqMapComplaintUnitsList: TUniQuery
Connection = ucENTCAD
SQL.Strings = (
'SELECT'
' ca.COMPLAINTID,'
' ca.UNITID,'
' ca.UNITNAME,'
' ca.DATEDISPATCHED,'
' ca.DATERESPONDED,'
' ca.DATEARRIVED,'
' ca.DATECLEARED,'
' ca.LOCATION'
'FROM CFS_ACTIVE ca'
'WHERE ca.COMPLAINTID IN ('
' SELECT c.COMPLAINTID'
' FROM COMPLAINT_ACTIVE c'
' WHERE c.XCOORD IS NOT NULL'
' AND c.YCOORD IS NOT NULL'
')'
'ORDER BY ca.COMPLAINTID, ca.DATEDISPATCHED;')
Active = True
Left = 470
Top = 242
object uqMapComplaintUnitsListCOMPLAINTID: TFloatField
FieldName = 'COMPLAINTID'
end
object uqMapComplaintUnitsListUNITID: TFloatField
FieldName = 'UNITID'
end
object uqMapComplaintUnitsListUNITNAME: TStringField
FieldName = 'UNITNAME'
Size = 10
end
object uqMapComplaintUnitsListDATEDISPATCHED: TDateTimeField
FieldName = 'DATEDISPATCHED'
end
object uqMapComplaintUnitsListDATERESPONDED: TDateTimeField
FieldName = 'DATERESPONDED'
end
object uqMapComplaintUnitsListDATEARRIVED: TDateTimeField
FieldName = 'DATEARRIVED'
end
object uqMapComplaintUnitsListDATECLEARED: TDateTimeField
FieldName = 'DATECLEARED'
end
object uqMapComplaintUnitsListLOCATION: TStringField
FieldName = 'LOCATION'
Size = 30
end
end
end end
...@@ -9,9 +9,6 @@ uses ...@@ -9,9 +9,6 @@ uses
type type
TApiDatabaseModule = class(TDataModule) TApiDatabaseModule = class(TDataModule)
ucEnvoy: TUniConnection;
PostgreSQLUniProvider1: TPostgreSQLUniProvider;
UniQuery1: TUniQuery;
OracleUniProvider1: TOracleUniProvider; OracleUniProvider1: TOracleUniProvider;
uqBooking: TUniQuery; uqBooking: TUniQuery;
uqMapUnits: TUniQuery; uqMapUnits: TUniQuery;
...@@ -121,6 +118,16 @@ type ...@@ -121,6 +118,16 @@ type
uqComplaintDetailsDATERESPONDED: TDateTimeField; uqComplaintDetailsDATERESPONDED: TDateTimeField;
uqComplaintDetailsDATEARRIVED: TDateTimeField; uqComplaintDetailsDATEARRIVED: TDateTimeField;
uqComplaintDetailsDATECLEARED: TDateTimeField; uqComplaintDetailsDATECLEARED: TDateTimeField;
uqMapComplaintUnitsList: TUniQuery;
uqMapComplaintUnitsListCOMPLAINTID: TFloatField;
uqMapComplaintUnitsListUNITID: TFloatField;
uqMapComplaintUnitsListUNITNAME: TStringField;
uqMapComplaintUnitsListDATEDISPATCHED: TDateTimeField;
uqMapComplaintUnitsListDATERESPONDED: TDateTimeField;
uqMapComplaintUnitsListDATEARRIVED: TDateTimeField;
uqMapComplaintUnitsListDATECLEARED: TDateTimeField;
uqMapComplaintUnitsListLOCATION: TStringField;
uqMapComplaintsADDRESS: TStringField;
procedure uqComplaintListCalcFields(DataSet: TDataSet); procedure uqComplaintListCalcFields(DataSet: TDataSet);
procedure uqMapComplaintsCalcFields(DataSet: TDataSet); procedure uqMapComplaintsCalcFields(DataSet: TDataSet);
private private
...@@ -129,7 +136,6 @@ type ...@@ -129,7 +136,6 @@ type
function DerivePriorityKeyFromPriorityString(const priorityString: string): string; function DerivePriorityKeyFromPriorityString(const priorityString: string): string;
function HandleUniqueFilenames(const category: string): string; function HandleUniqueFilenames(const category: string): string;
function BadgeCounts(const BaseQuery: TUniQuery): Integer; function BadgeCounts(const BaseQuery: TUniQuery): Integer;
class procedure ExecSQL(const SQL: string);
end; end;
var var
...@@ -141,19 +147,6 @@ implementation ...@@ -141,19 +147,6 @@ implementation
{$R *.dfm} {$R *.dfm}
class procedure TApiDatabaseModule.ExecSQL(const SQL: string);
var
DB: TApiDatabaseModule;
begin
DB := TApiDatabaseModule.Create(nil);
try
DB.UniQuery1.SQL.Text := SQL;
DB.UniQuery1.ExecSQL;
finally
DB.Free;
end;
end;
procedure TApiDatabaseModule.uqComplaintListCalcFields(DataSet: TDataSet); procedure TApiDatabaseModule.uqComplaintListCalcFields(DataSet: TDataSet);
var var
raw: string; raw: string;
......
...@@ -57,7 +57,6 @@ begin ...@@ -57,7 +57,6 @@ begin
begin begin
Open; Open;
try try
// NOTE: your TUniQuery exposes fields named COMPLAINTS and UNITS
Result.AddPair('BadgeComplaints', TJSONNumber.Create(FieldByName('COMPLAINTS').AsInteger)); Result.AddPair('BadgeComplaints', TJSONNumber.Create(FieldByName('COMPLAINTS').AsInteger));
Result.AddPair('BadgeUnits', TJSONNumber.Create(FieldByName('UNITS').AsInteger)); Result.AddPair('BadgeUnits', TJSONNumber.Create(FieldByName('UNITS').AsInteger));
finally finally
...@@ -80,60 +79,115 @@ var ...@@ -80,60 +79,115 @@ var
data: TJSONArray; data: TJSONArray;
emitted: Integer; emitted: Integer;
item: TJSONObject; item: TJSONObject;
UnitsByComplaintMap: TObjectDictionary<string,TJSONArray>;
complaintId: string;
unitArray: TJSONArray;
unitStatus: string;
latestUpdate: TDateTime;
unitObj: TJSONObject;
begin begin
Logger.Log(3, '---TApiService.GetComplaintMap initiated'); Logger.Log(3,'---TApiService.GetComplaintMap initiated');
Result := TJSONObject.Create; Result:=TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result); TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
data := TJSONArray.Create; data:=TJSONArray.Create;
UnitsByComplaintMap:=TObjectDictionary<string,TJSONArray>.Create([doOwnsValues]);
try try
emitted := 0; ApiDB.uqMapComplaintUnitsList.Open;
with ApiDB.uqMapComplaints do while not ApiDB.uqMapComplaintUnitsList.Eof do
begin begin
Open; complaintId:=ApiDB.uqMapComplaintUnitsListCOMPLAINTID.AsString;
First; if not UnitsByComplaintMap.TryGetValue(complaintId,unitArray) then
while not Eof do
begin begin
if ApiDB.uqMapComplaintsLAT.IsNull or ApiDB.uqMapComplaintsLNG.IsNull then unitArray:=TJSONArray.Create;
begin UnitsByComplaintMap.Add(complaintId,unitArray);
Next; end;
Continue;
end;
item := TJSONObject.Create;
item.AddPair('ComplaintId', ApiDB.uqMapComplaintsCOMPLAINTID.AsString); unitStatus:='Dispatched';
item.AddPair('DispatchDistrict', ApiDB.uqMapComplaintsDISPATCHDISTRICT.AsString); if not ApiDB.uqMapComplaintUnitsListDATECLEARED.IsNull then
item.AddPair('DispatchCodeDesc', ApiDB.uqMapComplaintsDISPATCH_CODE_DESC.AsString); unitStatus:='Cleared'
else if not ApiDB.uqMapComplaintUnitsListDATEARRIVED.IsNull then
unitStatus:='On Scene'
else if not ApiDB.uqMapComplaintUnitsListDATERESPONDED.IsNull then
unitStatus:='Enroute';
latestUpdate:=0;
if not ApiDB.uqMapComplaintUnitsListDATEDISPATCHED.IsNull then
latestUpdate:=ApiDB.uqMapComplaintUnitsListDATEDISPATCHED.AsDateTime;
if (not ApiDB.uqMapComplaintUnitsListDATERESPONDED.IsNull) and
(ApiDB.uqMapComplaintUnitsListDATERESPONDED.AsDateTime>latestUpdate) then
latestUpdate:=ApiDB.uqMapComplaintUnitsListDATERESPONDED.AsDateTime;
if (not ApiDB.uqMapComplaintUnitsListDATEARRIVED.IsNull) and
(ApiDB.uqMapComplaintUnitsListDATEARRIVED.AsDateTime>latestUpdate) then
latestUpdate:=ApiDB.uqMapComplaintUnitsListDATEARRIVED.AsDateTime;
if (not ApiDB.uqMapComplaintUnitsListDATECLEARED.IsNull) and
(ApiDB.uqMapComplaintUnitsListDATECLEARED.AsDateTime>latestUpdate) then
latestUpdate:=ApiDB.uqMapComplaintUnitsListDATECLEARED.AsDateTime;
unitObj:=TJSONObject.Create;
unitObj.AddPair('Unit',ApiDB.uqMapComplaintUnitsListUNITNAME.AsString);
unitObj.AddPair('Status',unitStatus);
if latestUpdate<>0 then
unitObj.AddPair('Updated',FormatDateTime('yyyy-mm-dd hh:nn:ss',latestUpdate))
else
unitObj.AddPair('Updated','');
unitArray.AddElement(unitObj);
ApiDB.uqMapComplaintUnitsList.Next;
end;
item.AddPair('DispatchCodeCategory', ApiDB.uqMapComplaintsDISPATCHCODECATEGORY.AsString); try
item.AddPair('Priority', ApiDB.uqMapComplaintsPRIORITY.AsString); emitted:=0;
item.AddPair('priorityKey', ApiDB.uqMapComplaints.FieldByName('priorityKey').AsString); ApiDB.uqMapComplaints.Open;
item.AddPair('pngName', ApiDB.uqMapComplaints.FieldByName('pngName').AsString); while not ApiDB.uqMapComplaints.Eof do
begin
item:=TJSONObject.Create;
item.AddPair('ComplaintId',ApiDB.uqMapComplaintsCOMPLAINTID.AsString);
item.AddPair('DispatchDistrict',ApiDB.uqMapComplaintsDISPATCHDISTRICT.AsString);
item.AddPair('DispatchCodeDesc',ApiDB.uqMapComplaintsDISPATCH_CODE_DESC.AsString);
item.AddPair('DispatchCodeCategory',ApiDB.uqMapComplaintsDISPATCHCODECATEGORY.AsString);
item.AddPair('Priority',ApiDB.uqMapComplaintsPRIORITY.AsString);
item.AddPair('priorityKey',ApiDB.uqMapComplaintspriorityKey.AsString);
item.AddPair('pngName',ApiDB.uqMapComplaintspngName.AsString);
item.AddPair('Address',ApiDB.uqMapComplaintsADDRESS.AsString);
complaintId:=ApiDB.uqMapComplaintsCOMPLAINTID.AsString;
if UnitsByComplaintMap.TryGetValue(complaintId,unitArray) then
item.AddPair('Units',TJSONArray(unitArray.Clone))
else
item.AddPair('Units',TJSONArray.Create);
item.AddPair('Lat', TJSONNumber.Create(ApiDB.uqMapComplaintsLAT.AsFloat)); item.AddPair('Lat',TJSONNumber.Create(ApiDB.uqMapComplaintsLAT.AsFloat));
item.AddPair('Lng', TJSONNumber.Create(ApiDB.uqMapComplaintsLNG.AsFloat)); item.AddPair('Lng',TJSONNumber.Create(ApiDB.uqMapComplaintsLNG.AsFloat));
data.AddElement(item); data.AddElement(item);
Inc(emitted); Inc(emitted);
Next; ApiDB.uqMapComplaints.Next;
end; end;
end;
Result.AddPair('count', TJSONNumber.Create(data.Count)); Result.AddPair('count',TJSONNumber.Create(data.Count));
Result.AddPair('returned', TJSONNumber.Create(emitted)); Result.AddPair('returned',TJSONNumber.Create(emitted));
Result.AddPair('data', data); Result.AddPair('data',data);
except Logger.Log(3,'---TApiService.GetComplaintMap End (returned='+emitted.ToString+')');
data.Free; except
Logger.Log(3, '---TApiService.GetComplaintMap End (error)'); on E: Exception do
raise EXDataHttpException.Create(500, 'Failed to load complaint map'); begin
FreeAndNil(data);
Logger.Log(1,'GetComplaintMap error: '+E.Message);
raise EXDataHttpException.Create(500,'Failed to load complaint map');
end;
end;
finally
UnitsByComplaintMap.Free;
end; end;
Logger.Log(3, '---TApiService.GetComplaintMap End');
end; end;
function TApiService.GetUnitMap: TJSONObject; function TApiService.GetUnitMap: TJSONObject;
var var
data: TJSONArray; data: TJSONArray;
...@@ -211,7 +265,6 @@ begin ...@@ -211,7 +265,6 @@ begin
var item := TJSONObject.Create; var item := TJSONObject.Create;
// Add a section header only when the district changes
var curDistrict := ApiDB.uqComplaintListDISPATCHDISTRICT.AsString; var curDistrict := ApiDB.uqComplaintListDISPATCHDISTRICT.AsString;
if not SameText(curDistrict, lastDistrict) then if not SameText(curDistrict, lastDistrict) then
item.AddPair('DistrictHeader', curDistrict); item.AddPair('DistrictHeader', curDistrict);
...@@ -220,11 +273,9 @@ begin ...@@ -220,11 +273,9 @@ begin
var districtSector := ApiDB.uqComplaintListDISTRICT_DESC.AsString + ApiDB.uqComplaintListSECTOR_DESC.AsString; var districtSector := ApiDB.uqComplaintListDISTRICT_DESC.AsString + ApiDB.uqComplaintListSECTOR_DESC.AsString;
item.AddPair('DistrictSector', districtSector); item.AddPair('DistrictSector', districtSector);
// existing color hex
var colorVal := ApiDB.uqComplaintListPRIORITY_COLOR.AsInteger; var colorVal := ApiDB.uqComplaintListPRIORITY_COLOR.AsInteger;
item.AddPair('PriorityColor', '#' + IntToHex(colorVal and $FFFFFF, 6)); item.AddPair('PriorityColor', '#' + IntToHex(colorVal and $FFFFFF, 6));
// Text is white only for the deep blue (255 = $0000FF), black otherwise
if (colorVal and $FFFFFF) = $0000FF then if (colorVal and $FFFFFF) = $0000FF then
item.AddPair('PriorityTextColor', '#FFFFFF') item.AddPair('PriorityTextColor', '#FFFFFF')
else else
......
...@@ -51,10 +51,6 @@ object AuthDatabase: TAuthDatabase ...@@ -51,10 +51,6 @@ object AuthDatabase: TAuthDatabase
Left = 249 Left = 249
Top = 45 Top = 45
end end
object PostgreSQLUniProvider1: TPostgreSQLUniProvider
Left = 276
Top = 156
end
object OracleUniProvider1: TOracleUniProvider object OracleUniProvider1: TOracleUniProvider
Left = 94 Left = 94
Top = 152 Top = 152
...@@ -78,7 +74,7 @@ object AuthDatabase: TAuthDatabase ...@@ -78,7 +74,7 @@ object AuthDatabase: TAuthDatabase
end end
object uqBooking: TUniQuery object uqBooking: TUniQuery
Connection = ucBooking Connection = ucBooking
Left = 98 Left = 88
Top = 44 Top = 48
end end
end end
...@@ -10,7 +10,6 @@ type ...@@ -10,7 +10,6 @@ type
TAuthDatabase = class(TDataModule) TAuthDatabase = class(TDataModule)
uq: TUniQuery; uq: TUniQuery;
uqMisc: TUniQuery; uqMisc: TUniQuery;
PostgreSQLUniProvider1: TPostgreSQLUniProvider;
uquser_id: TLargeintField; uquser_id: TLargeintField;
uqusername: TStringField; uqusername: TStringField;
uqpassword: TMemoField; uqpassword: TMemoField;
...@@ -70,21 +69,6 @@ begin ...@@ -70,21 +69,6 @@ begin
end; end;
end; end;
Logger.Log(1, '');
Logger.Log(1, 'Loading Twilio settings...');
var twilioSID := iniFile.ReadString('Twilio', 'AccountSID', '');
if twilioSID.IsEmpty then
Logger.Log(1, 'Twilio->AccountSID: Entry not found')
else
Logger.Log(1, 'Twilio->AccountSID Found');
var twilioAuth := iniFile.ReadString('Twilio', 'AuthHeader', '');
if twilioAuth.IsEmpty then
Logger.Log(1, 'Twilio->AuthHeader: Entry not found')
else
Logger.Log(1, 'Twilio->AuthHeader Found');
finally finally
iniFile.Free; iniFile.Free;
end; end;
......
...@@ -15,30 +15,11 @@ type ...@@ -15,30 +15,11 @@ type
FFileLogLevelFromIni: Boolean; FFileLogLevelFromIni: Boolean;
FLogFileNum: Integer; FLogFileNum: Integer;
FLogFileNumFromIni: Boolean; FLogFileNumFromIni: Boolean;
FEmailPolling: Boolean;
FEmailPollingFromIni: Boolean;
FEmailPollingInterval: Integer;
FEmailPollingIntervalFromIni: Boolean;
FWebClientVersion: string; FWebClientVersion: string;
FWebClientVersionFromIni: Boolean; FWebClientVersionFromIni: Boolean;
FTwilioUpdateTime: Integer;
FTwilioUpdateTimeFromIni: Boolean;
// [Database] // [Database]
FDatabaseServer: string;
FDatabaseServerFromIni: Boolean;
FDatabaseName: string;
FDatabaseNameFromIni: Boolean;
FDatabaseUsername: string;
FDatabaseUsernameFromIni: Boolean;
FDatabasePassword: string;
FDatabasePasswordFromIni: Boolean;
// [Twilio]
FTwilioSID: string;
FTwilioSIDFromIni: Boolean;
FTwilioAuthHeader: string;
FTwilioAuthHeaderFromIni: Boolean;
public public
constructor Create; constructor Create;
...@@ -49,30 +30,12 @@ type ...@@ -49,30 +30,12 @@ type
property FileLogLevelFromIni: Boolean read FFileLogLevelFromIni; property FileLogLevelFromIni: Boolean read FFileLogLevelFromIni;
property LogFileNum: Integer read FLogFileNum; property LogFileNum: Integer read FLogFileNum;
property LogFileNumFromIni: Boolean read FLogFileNumFromIni; property LogFileNumFromIni: Boolean read FLogFileNumFromIni;
property EmailPolling: Boolean read FEmailPolling;
property EmailPollingFromIni: Boolean read FEmailPollingFromIni;
property EmailPollingInterval: Integer read FEmailPollingInterval;
property EmailPollingIntervalFromIni: Boolean read FEmailPollingIntervalFromIni;
property WebClientVersion: string read FWebClientVersion; property WebClientVersion: string read FWebClientVersion;
property WebClientVersionFromIni: Boolean read FWebClientVersionFromIni; property WebClientVersionFromIni: Boolean read FWebClientVersionFromIni;
property TwilioUpdateTime: Integer read FTwilioUpdateTime;
property TwilioUpdateTimeFromIni: Boolean read FTwilioUpdateTimeFromIni;
// [Database] // [Database]
property DatabaseServer: string read FDatabaseServer;
property DatabaseServerFromIni: Boolean read FDatabaseServerFromIni;
property DatabaseName: string read FDatabaseName;
property DatabaseNameFromIni: Boolean read FDatabaseNameFromIni;
property DatabaseUsername: string read FDatabaseUsername;
property DatabaseUsernameFromIni: Boolean read FDatabaseUsernameFromIni;
property DatabasePassword: string read FDatabasePassword;
property DatabasePasswordFromIni: Boolean read FDatabasePasswordFromIni;
// [Twilio]
property TwilioSID: string read FTwilioSID;
property TwilioSIDFromIni: Boolean read FTwilioSIDFromIni;
property TwilioAuthHeader: string read FTwilioAuthHeader;
property TwilioAuthHeaderFromIni: Boolean read FTwilioAuthHeaderFromIni;
end; end;
procedure LoadIniEntries; procedure LoadIniEntries;
...@@ -107,37 +70,11 @@ begin ...@@ -107,37 +70,11 @@ begin
FLogFileNum := iniFile.ReadInteger('Settings', 'LogFileNum', 0); FLogFileNum := iniFile.ReadInteger('Settings', 'LogFileNum', 0);
FLogFileNumFromIni := iniFile.ValueExists('Settings', 'LogFileNum'); FLogFileNumFromIni := iniFile.ValueExists('Settings', 'LogFileNum');
FEmailPolling := iniFile.ReadBool('Settings', 'EmailPolling', False);
FEmailPollingFromIni := iniFile.ValueExists('Settings', 'EmailPolling');
FEmailPollingInterval := iniFile.ReadInteger('Settings', 'EmailPollingInterval', 1);
FEmailPollingIntervalFromIni := iniFile.ValueExists('Settings', 'EmailPollingInterval');
FWebClientVersion := iniFile.ReadString('Settings', 'webClientVersion', ''); FWebClientVersion := iniFile.ReadString('Settings', 'webClientVersion', '');
FWebClientVersionFromIni := iniFile.ValueExists('Settings', 'webClientVersion'); FWebClientVersionFromIni := iniFile.ValueExists('Settings', 'webClientVersion');
FTwilioUpdateTime := iniFile.ReadInteger('Settings', 'TwilioUpdateTime', 0);
FTwilioUpdateTimeFromIni := iniFile.ValueExists('Settings', 'TwilioUpdateTime');
// [Database] // [Database]
FDatabaseServer := iniFile.ReadString('Database', 'Server', 'localhost');
FDatabaseServerFromIni := iniFile.ValueExists('Database', 'Server');
FDatabaseName := iniFile.ReadString('Database', 'Database', 'envoy_db');
FDatabaseNameFromIni := iniFile.ValueExists('Database', 'Database');
FDatabaseUsername := iniFile.ReadString('Database', 'Username', 'postgres');
FDatabaseUsernameFromIni := iniFile.ValueExists('Database', 'Username');
FDatabasePassword := iniFile.ReadString('Database', 'Password', '');
FDatabasePasswordFromIni := iniFile.ValueExists('Database', 'Password');
// [Twilio]
FTwilioSID := iniFile.ReadString('Twilio', 'AccountSID', '');
FTwilioSIDFromIni := iniFile.ValueExists('Twilio', 'AccountSID');
FTwilioAuthHeader := iniFile.ReadString('Twilio', 'AuthHeader', '');
FTwilioAuthHeaderFromIni := iniFile.ValueExists('Twilio', 'AuthHeader');
finally finally
iniFile.Free; iniFile.Free;
end; end;
......
// Uses Twilio.Data.Module for the rest api calls. Simply for testing querys.
// Visual aspect is for testing purposes only and has no affect on the client.
// Authors:
// Cameron Hayes
// Elias Serraf
// Mac ...
unit Data;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, AdvUtil, Data.DB, Vcl.Grids, AdvObj,
BaseGrid, AdvGrid, DBAdvGrid, MemDS, DBAccess, Uni, Vcl.StdCtrls, Vcl.Mask,
vcl.wwdbedit, vcl.wwdotdot, vcl.wwdbcomb, REST.Client, REST.Types, System.JSON,
System.Generics.Collections, AdvEdit, vcl.wwdblook, vcl.wwdbdatetimepicker,
System.Hash;
type
TFData = class(TForm)
dsCalls: TDataSource;
btnFind: TButton;
Memo1: TMemo;
btnGetCalls: TButton;
txtPhoneNum: TAdvEdit;
DBAdvGrid1: TDBAdvGrid;
DBAdvGrid2: TDBAdvGrid;
dsRecordings: TDataSource;
wwlcStore: TwwDBLookupCombo;
dtpCallsDate1: TwwDBDateTimePicker;
FullUpdate: TButton;
lblStartDate: TLabel;
lblLocation: TLabel;
dtpCallsDate2: TwwDBDateTimePicker;
Label1: TLabel;
edtUsername: TEdit;
edtPassword: TEdit;
lblHash: TLabel;
btnAddUser: TButton;
lblHash2: TLabel;
uqUsers: TUniQuery;
cbAdmin: TCheckBox;
uqUsersuser_id: TLargeintField;
uqUsersusername: TStringField;
uqUserspassword: TMemoField;
uqUsersdate_created: TStringField;
uqUsersadmin: TBooleanField;
edtFullName: TEdit;
edtPhoneNumber: TEdit;
edtEmailAddress: TEdit;
uqUsersemail: TMemoField;
uqUsersphone_number: TStringField;
uqUsersfull_name: TStringField;
uqUsersactive: TBooleanField;
procedure btnFindClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure btnGetCallsClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FullUpdateClick(Sender: TObject);
procedure wwlcStoreCloseUp(Sender: TObject; LookupTable,
FillTable: TDataSet; modified: Boolean);
procedure btnAddUserClick(Sender: TObject);
procedure addUser();
private
{ Private declarations }
accountSID: string;
authHeader: string;
public
{ Public declarations }
end;
var
FData: TFData;
implementation
{$R *.dfm}
uses Api.Database, Twilio.Data.Module, uLibrary;
procedure TFData.FormCreate(Sender: TObject);
begin
TwilioDataModule := TTwilioDataModule.Create(Self);
end;
procedure TFData.FormDestroy(Sender: TObject);
begin
TwilioDataModule.Free;
end;
procedure TFData.FullUpdateClick(Sender: TObject);
var
count: string;
begin
count := TwilioDataModule.FullUpdate();
Memo1.Lines.Add(count);
end;
procedure TFData.wwlcStoreCloseUp(Sender: TObject; LookupTable,
FillTable: TDataSet; modified: Boolean);
begin
if wwlcStore.Text = '' then
txtPhoneNum.Text := ''
else
txtPhoneNum.Text := wwlcStore.LookupValue;
end;
procedure TFData.btnGetCallsClick(Sender: TObject);
// Gets 50 calls and adds them to the database if they havent seen an earlier call
begin
TwilioDataModule.GetCalls(txtPhoneNum.Text, 50, 0 );
end;
procedure TFData.btnAddUserClick(Sender: TObject);
var
dateCreated: TDateTime;
hashString: string;
SQL: string;
begin
addUser();
end;
procedure TFData.addUser();
var
dateCreated: TDateTime;
hashString: string;
SQL: string;
username: string;
begin
dateCreated := now;
hashString := DateTimeToStr(dateCreated) + edtPassword.Text;
lblHash.Caption := THashSHA2.GetHashString(
hashString,
THashSHA2.TSHA2Version.SHA512).ToUpper;
lblHash2.Caption := IntToStr(length(DateTimeToStr(dateCreated)));
username := edtUsername.Text;
username := username.ToLower;
SQL := 'select * from envoy.users where username = ' + QuotedStr(username);
uqUsers.Close;
uqUsers.SQL.Text := sql;
uqUsers.Open;
if uqUsers.IsEmpty then
begin
uqUsers.Insert;
uqUsersusername.AsString := username;
uqUserspassword.AsString := THashSHA2.GetHashString(hashString,
THashSHA2.TSHA2Version.SHA512).ToUpper;
uqUsersdate_created.AsString := DateTimeToStr(dateCreated);
uqUsersadmin.AsBoolean := cbAdmin.Checked;
uqUsersphone_number.AsString := edtPhoneNumber.Text;
uqUsersemail.AsString := edtEmailAddress.Text;
uqUsersfull_name.AsString := edtFullName.Text;
uqUsers.Post;
lblHash2.Caption := 'Added';
end
else
lblHash2.Caption := 'Username already taken';
end;
procedure TFData.btnFindClick(Sender: TObject);
// Retrieves calls from a specific number from the database.
// SQL: SQL statement to retrieve calls from the database
// whereSQL: where section of the SQL that is built in the function
var
SQL: string;
whereSQL: string;
begin
//TwilioDataModule.ShowCalls(txtPhoneNum.Text);
whereSQL := 'where ';
if wwlcStore.Text <> '' then
whereSQL := whereSQL + 'to_formatted = ' + QuotedStr(txtPhoneNum.Text);
if dtpCallsDate1.Text <> '' then
if whereSQL = 'where ' then
whereSQL := whereSQL + 'date_created > ' + QuotedStr(dtpCallsDate1.Text)
else
whereSQL := whereSQL + 'AND date_created > ' + QuotedStr(dtpCallsDate1.Text);
if dtpCallsDate2.Text <> '' then
if whereSQL = 'where ' then
whereSQL := whereSQL + 'date_created <= ' + QuotedStr(dtpCallsDate2.Text)
else
whereSQL := whereSQL + 'AND date_created <= ' + QuotedStr(dtpCallsDate2.Text);
if whereSQL = 'where ' then
whereSQL := '';
SQL := 'select * from envoy.calls '+ whereSQL + ' order by date_created desc';
Memo1.Lines.Add(SQL);
doQuery(TwilioDataModule.uqCalls, SQL);
DBAdvGrid1.AutoSizeColumns(true);
end;
end.
...@@ -17,7 +17,7 @@ object FMain: TFMain ...@@ -17,7 +17,7 @@ object FMain: TFMain
TextHeight = 13 TextHeight = 13
object memoInfo: TMemo object memoInfo: TMemo
Left = 8 Left = 8
Top = 40 Top = 39
Width = 744 Width = 744
Height = 549 Height = 549
Anchors = [akLeft, akTop, akRight, akBottom] Anchors = [akLeft, akTop, akRight, akBottom]
...@@ -33,22 +33,13 @@ object FMain: TFMain ...@@ -33,22 +33,13 @@ object FMain: TFMain
TabOrder = 1 TabOrder = 1
OnClick = btnApiSwaggerUIClick OnClick = btnApiSwaggerUIClick
end end
object btnData: TButton
Left = 525
Top = 8
Width = 75
Height = 25
Caption = 'Data'
TabOrder = 2
OnClick = btnDataClick
end
object btnExit: TButton object btnExit: TButton
Left = 671 Left = 671
Top = 8 Top = 8
Width = 75 Width = 75
Height = 25 Height = 25
Caption = 'Exit' Caption = 'Exit'
TabOrder = 3 TabOrder = 2
OnClick = btnExitClick OnClick = btnExitClick
end end
object btnAuthSwaggerUI: TButton object btnAuthSwaggerUI: TButton
...@@ -57,7 +48,7 @@ object FMain: TFMain ...@@ -57,7 +48,7 @@ object FMain: TFMain
Width = 100 Width = 100
Height = 25 Height = 25
Caption = 'Auth SwaggerUI' Caption = 'Auth SwaggerUI'
TabOrder = 4 TabOrder = 3
OnClick = btnAuthSwaggerUIClick OnClick = btnAuthSwaggerUIClick
end end
object initTimer: TTimer object initTimer: TTimer
......
...@@ -13,20 +13,17 @@ type ...@@ -13,20 +13,17 @@ type
TFMain = class(TForm) TFMain = class(TForm)
memoInfo: TMemo; memoInfo: TMemo;
btnApiSwaggerUI: TButton; btnApiSwaggerUI: TButton;
btnData: TButton;
btnExit: TButton; btnExit: TButton;
initTimer: TTimer; initTimer: TTimer;
btnAuthSwaggerUI: TButton; btnAuthSwaggerUI: TButton;
ExeInfo1: TExeInfo; ExeInfo1: TExeInfo;
procedure btnApiSwaggerUIClick(Sender: TObject); procedure btnApiSwaggerUIClick(Sender: TObject);
procedure btnDataClick(Sender: TObject);
procedure btnExitClick(Sender: TObject); procedure btnExitClick(Sender: TObject);
procedure ContactFormData(AText: String); procedure ContactFormData(AText: String);
procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure initTimerTimer(Sender: TObject); procedure initTimerTimer(Sender: TObject);
procedure btnAuthSwaggerUIClick(Sender: TObject); procedure btnAuthSwaggerUIClick(Sender: TObject);
strict private strict private
phoneDict: TDictionary<string, string>;
procedure StartServers; procedure StartServers;
function LogValue(const LabelName: string; const Value: string; FromIni: Boolean): string; function LogValue(const LabelName: string; const Value: string; FromIni: Boolean): string;
end; end;
...@@ -41,7 +38,8 @@ uses ...@@ -41,7 +38,8 @@ uses
Common.Config, Common.Config,
Common.Ini, Common.Ini,
Sparkle.Utils, Sparkle.Utils,
Data, Twilio.Data.Module, Api.Database, System.StrUtils; Api.Database,
System.StrUtils;
{$R *.dfm} {$R *.dfm}
...@@ -62,14 +60,6 @@ begin ...@@ -62,14 +60,6 @@ begin
ShellExecute(Handle, 'open', PChar(TSparkleUtils.CombineUrlFast(AuthServerModule.XDataServer.BaseUrl, 'swaggerui')), nil, nil, SW_SHOWNORMAL); ShellExecute(Handle, 'open', PChar(TSparkleUtils.CombineUrlFast(AuthServerModule.XDataServer.BaseUrl, 'swaggerui')), nil, nil, SW_SHOWNORMAL);
end; end;
procedure TFMain.btnDataClick(Sender: TObject);
begin
FData := TFData.Create(Self);
FData.ShowModal;
FData.Free;
end;
procedure TFMain.ContactFormData(AText: String); procedure TFMain.ContactFormData(AText: String);
begin begin
if memoInfo.CanFocus then if memoInfo.CanFocus then
...@@ -90,7 +80,6 @@ end; ...@@ -90,7 +80,6 @@ end;
procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction); procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin begin
phoneDict.Free;
ServerConfig.Free; ServerConfig.Free;
IniEntries.Free; IniEntries.Free;
AuthServerModule.Free; AuthServerModule.Free;
...@@ -118,16 +107,15 @@ begin ...@@ -118,16 +107,15 @@ begin
Logger.Log(1, '--- Settings ---'); Logger.Log(1, '--- Settings ---');
Logger.Log(1, LogValue('--Settings->LogFileNum', IniEntries.LogFileNum.ToString, IniEntries.LogFileNumFromIni)); Logger.Log(1, LogValue('--Settings->LogFileNum', IniEntries.LogFileNum.ToString, IniEntries.LogFileNumFromIni));
Logger.Log(1, LogValue('--Settings->webClientVersion', IniEntries.WebClientVersion, IniEntries.WebClientVersionFromIni)); Logger.Log(1, LogValue('--Settings->webClientVersion', IniEntries.WebClientVersion, IniEntries.WebClientVersionFromIni));
Logger.Log(1, LogValue('--Settings->TwilioUpdateTime', IniEntries.TwilioUpdateTime.ToString, IniEntries.TwilioUpdateTimeFromIni));
Logger.Log(1, '');
Logger.Log(1, '--- Database ---'); // Logger.Log(1, '--- Database ---');
Logger.Log(1, LogValue('--Database->Server', IniEntries.DatabaseServer, IniEntries.DatabaseServerFromIni)); // Logger.Log(1, LogValue('--Database->Server', IniEntries.DatabaseServer, IniEntries.DatabaseServerFromIni));
Logger.Log(1, LogValue('--Database->Database', IniEntries.DatabaseName, IniEntries.DatabaseNameFromIni)); // Logger.Log(1, LogValue('--Database->Database', IniEntries.DatabaseName, IniEntries.DatabaseNameFromIni));
Logger.Log(1, LogValue('--Database->Username', IniEntries.DatabaseUsername, IniEntries.DatabaseUsernameFromIni)); // Logger.Log(1, LogValue('--Database->Username', IniEntries.DatabaseUsername, IniEntries.DatabaseUsernameFromIni));
Logger.Log(1, LogValue('--Database->Password', IniEntries.DatabasePassword, IniEntries.DatabasePasswordFromIni)); // Logger.Log(1, LogValue('--Database->Password', IniEntries.DatabasePassword, IniEntries.DatabasePasswordFromIni));
Logger.Log(1, '');
Logger.Log(1, '');
Logger.Log(1, '--- URLs ---');
try try
AuthServerModule := TAuthServerModule.Create(Self); AuthServerModule := TAuthServerModule.Create(Self);
AuthServerModule.StartAuthServer(ServerConfig.url, AUTH_MODEL); AuthServerModule.StartAuthServer(ServerConfig.url, AUTH_MODEL);
......
object TwilioDataModule: TTwilioDataModule
OnCreate = DataModuleCreate
OnDestroy = DataModuleDestroy
Height = 338
Width = 537
object ucEnvoy: TUniConnection
ProviderName = 'PostgreSQL'
SpecificOptions.Strings = (
'PostgreSQL.Schema=envoy')
LoginPrompt = False
Left = 65
Top = 89
end
object PostgreSQLUniProvider1: TPostgreSQLUniProvider
Left = 226
Top = 88
end
object UniQuery1: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'')
Left = 361
Top = 88
end
object uqLocations: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'select * from locations')
Left = 130
Top = 172
object uqLocationslocation: TStringField
DisplayWidth = 30
FieldName = 'location'
Required = True
Size = 30
end
object uqLocationsphone_number: TStringField
DisplayWidth = 14
FieldName = 'phone_number'
Required = True
Visible = False
Size = 14
end
object uqLocationsphone_number_formatted: TStringField
FieldName = 'phone_number_formatted'
Size = 14
end
end
object uqRecordings: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'select * from recordings')
Left = 224
Top = 174
object uqRecordingsaccount_sid: TStringField
FieldName = 'account_sid'
Size = 34
end
object uqRecordingsapi_version: TStringField
FieldName = 'api_version'
Size = 10
end
object uqRecordingscall_sid: TStringField
FieldName = 'call_sid'
Size = 34
end
object uqRecordingsconference_sid: TStringField
FieldName = 'conference_sid'
Size = 34
end
object uqRecordingsdate_created: TDateTimeField
FieldName = 'date_created'
end
object uqRecordingsdate_updated: TDateTimeField
FieldName = 'date_updated'
end
object uqRecordingsstart_time: TDateTimeField
FieldName = 'start_time'
end
object uqRecordingsduration: TStringField
FieldName = 'duration'
Size = 4
end
object uqRecordingssid: TStringField
FieldName = 'sid'
Required = True
Size = 34
end
object uqRecordingsprice: TStringField
FieldName = 'price'
Size = 8
end
object uqRecordingsprice_unit: TStringField
FieldName = 'price_unit'
Size = 3
end
object uqRecordingsstatus: TStringField
FieldName = 'status'
Size = 10
end
object uqRecordingschannels: TStringField
FieldName = 'channels'
Size = 1
end
object uqRecordingssource: TStringField
FieldName = 'source'
Size = 10
end
object uqRecordingserror_code: TStringField
FieldName = 'error_code'
Size = 5
end
object uqRecordingsuri: TStringField
FieldName = 'uri'
Size = 106
end
object uqRecordingsencryption_details: TStringField
FieldName = 'encryption_details'
Size = 30
end
object uqRecordingsmedia_url: TStringField
FieldName = 'media_url'
Size = 123
end
object uqRecordingstranscription: TMemoField
FieldName = 'transcription'
BlobType = ftMemo
end
end
object uqCalls: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'select * from calls')
Left = 323
Top = 176
object uqCallsdate_updated: TDateTimeField
FieldName = 'date_updated'
end
object uqCallsprice_unit: TStringField
FieldName = 'price_unit'
Size = 3
end
object uqCallsparent_call_sid: TStringField
FieldName = 'parent_call_sid'
Size = 34
end
object uqCallscaller_name: TStringField
FieldName = 'caller_name'
Size = 30
end
object uqCallsduration: TStringField
FieldName = 'duration'
Size = 4
end
object uqCallsannotation: TMemoField
FieldName = 'annotation'
BlobType = ftMemo
end
object uqCallsanswered_by: TStringField
FieldName = 'answered_by'
Size = 30
end
object uqCallssid: TStringField
FieldName = 'sid'
Required = True
Size = 34
end
object uqCallsqueue_time: TStringField
FieldName = 'queue_time'
Size = 4
end
object uqCallsprice: TStringField
FieldName = 'price'
Size = 8
end
object uqCallsapi_version: TStringField
FieldName = 'api_version'
Size = 10
end
object uqCallsstatus: TStringField
FieldName = 'status'
Size = 10
end
object uqCallsdirection: TStringField
FieldName = 'direction'
Size = 8
end
object uqCallsstart_time: TDateTimeField
FieldName = 'start_time'
end
object uqCallsdate_created: TDateTimeField
FieldName = 'date_created'
end
object uqCallsfrom_formatted: TStringField
FieldName = 'from_formatted'
Size = 13
end
object uqCallsgroup_sid: TStringField
FieldName = 'group_sid'
Size = 34
end
object uqCallstrunk_sid: TStringField
FieldName = 'trunk_sid'
Size = 34
end
object uqCallsuri: TStringField
FieldName = 'uri'
Size = 101
end
object uqCallsaccount_sid: TStringField
FieldName = 'account_sid'
Size = 34
end
object uqCallsend_time: TDateTimeField
FieldName = 'end_time'
end
object uqCallsto_formatted: TStringField
FieldName = 'to_formatted'
Size = 13
end
object uqCallsphone_number_sid: TStringField
FieldName = 'phone_number_sid'
Size = 34
end
object uqCallsforwarded_from: TStringField
FieldName = 'forwarded_from'
Size = 12
end
end
end
...@@ -10,7 +10,6 @@ uses ...@@ -10,7 +10,6 @@ uses
Api.Server.Module in 'Source\Api.Server.Module.pas' {ApiServerModule: TDataModule}, Api.Server.Module in 'Source\Api.Server.Module.pas' {ApiServerModule: TDataModule},
Main in 'Source\Main.pas' {FMain}, Main in 'Source\Main.pas' {FMain},
Common.Logging in 'Source\Common.Logging.pas', Common.Logging in 'Source\Common.Logging.pas',
Data in 'Source\Data.pas' {FData},
Api.Database in 'Source\Api.Database.pas' {ApiDatabaseModule: TDataModule}, Api.Database in 'Source\Api.Database.pas' {ApiDatabaseModule: TDataModule},
Common.Middleware.Logging in 'Source\Common.Middleware.Logging.pas', Common.Middleware.Logging in 'Source\Common.Middleware.Logging.pas',
Common.Config in 'Source\Common.Config.pas', Common.Config in 'Source\Common.Config.pas',
...@@ -21,7 +20,6 @@ uses ...@@ -21,7 +20,6 @@ uses
Api.Service in 'Source\Api.Service.pas', Api.Service in 'Source\Api.Service.pas',
Auth.ServiceImpl in 'Source\Auth.ServiceImpl.pas', Auth.ServiceImpl in 'Source\Auth.ServiceImpl.pas',
Api.ServiceImpl in 'Source\Api.ServiceImpl.pas', Api.ServiceImpl in 'Source\Api.ServiceImpl.pas',
Twilio.Data.Module in 'Source\Twilio.Data.Module.pas' {TwilioDataModule: TDataModule},
App.Server.Module in 'Source\App.Server.Module.pas' {AppServerModule: TDataModule}, App.Server.Module in 'Source\App.Server.Module.pas' {AppServerModule: TDataModule},
Common.Ini in 'Source\Common.Ini.pas'; Common.Ini in 'Source\Common.Ini.pas';
......
...@@ -141,9 +141,6 @@ ...@@ -141,9 +141,6 @@
<Form>FMain</Form> <Form>FMain</Form>
</DCCReference> </DCCReference>
<DCCReference Include="Source\Common.Logging.pas"/> <DCCReference Include="Source\Common.Logging.pas"/>
<DCCReference Include="Source\Data.pas">
<Form>FData</Form>
</DCCReference>
<DCCReference Include="Source\Api.Database.pas"> <DCCReference Include="Source\Api.Database.pas">
<Form>ApiDatabaseModule</Form> <Form>ApiDatabaseModule</Form>
<DesignClass>TDataModule</DesignClass> <DesignClass>TDataModule</DesignClass>
...@@ -163,10 +160,6 @@ ...@@ -163,10 +160,6 @@
<DCCReference Include="Source\Api.Service.pas"/> <DCCReference Include="Source\Api.Service.pas"/>
<DCCReference Include="Source\Auth.ServiceImpl.pas"/> <DCCReference Include="Source\Auth.ServiceImpl.pas"/>
<DCCReference Include="Source\Api.ServiceImpl.pas"/> <DCCReference Include="Source\Api.ServiceImpl.pas"/>
<DCCReference Include="Source\Twilio.Data.Module.pas">
<Form>TwilioDataModule</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="Source\App.Server.Module.pas"> <DCCReference Include="Source\App.Server.Module.pas">
<Form>AppServerModule</Form> <Form>AppServerModule</Form>
<DesignClass>TDataModule</DesignClass> <DesignClass>TDataModule</DesignClass>
......
[Settings] [Settings]
LogFileNum=513 LogFileNum=525
webClientVersion=0.1.0 webClientVersion=0.1.0
TwilioUpdateTime=0
[Database]
Server=192.168.102.129
--Server=192.168.75.133
Database=envoy_db
Username=postgres
Password=postgreSQL
--Password=emsys01
[Twilio]
AccountSID=AC37aeef9c36a2cccbaecbadafc172b2ff
AuthHeader=Basic QUMzN2FlZWY5YzM2YTJjY2NiYWVjYmFkYWZjMTcyYjJmZjo5NzM5OTAwYTgyZmRlNjVlMzI2ODFmZjVmMmI5ZGZjZgo=
object FViewComplaintDetails: TFViewComplaintDetails object FViewComplaintDetails: TFViewComplaintDetails
Width = 640 Width = 640
Height = 480 Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS
object WebDBTableControl1: TWebDBTableControl
Left = 164
Top = 198
Width = 300
Height = 200
BorderColor = clSilver
ElementFont = efCSS
ElementHeaderClassName = 'thead-light'
ElementTableClassName = 'table table-striped table-bordered table-hover'
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'
Columns = <>
end
end end
<html> <!-- Sticky local navbar (Complaint Details) -->
<head> <div class="sticky-top">
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <nav class="navbar navbar-dark bg-primary py-2">
<title>TMS Web Project</title> <div class="container-fluid">
<style> <div class="row w-100 g-2 align-items-stretch">
</style> <div class="col">
</head> <div id="cdetails_title" class="navbar-brand mb-0 h5 text-white">Complaint Details</div>
<body> </div>
</body> <div class="col-auto">
</html> <button id="btn_close_complaint_details" type="button" class="btn btn-outline-light w-100 h-100">
\ No newline at end of file <i class="fa fa-times me-1"></i>
<span class="d-none d-sm-inline">Close</span>
</button>
</div>
</div>
</div>
</nav>
</div>
<!-- Complaint details content -->
<div class="container-fluid mt-2 complaint-details-page">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<!-- Outer card -->
<div class="card border-0 shadow-sm">
<div class="card-body p-3 bg-light">
<!-- Summary panel -->
<div class="card mb-3">
<div class="card-body py-2">
<div class="row gy-1">
<div class="col-5 col-sm-4 fw-semibold">Complaint</div>
<div class="col-7 col-sm-8" id="lbl_complaint_number"></div>
<div class="col-5 col-sm-4 fw-semibold">Priority</div>
<div class="col-7 col-sm-8" id="lbl_priority"></div>
<div class="col-5 col-sm-4 fw-semibold">Status</div>
<div class="col-7 col-sm-8" id="lbl_status"></div>
<div class="col-5 col-sm-4 fw-semibold">Dispatch Code</div>
<div class="col-7 col-sm-8" id="lbl_dispatch_code"></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>
<!-- Details table (thead in HTML, tbody filled by TWebDBListControl) -->
<div class="card">
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 60vh; overflow-y: auto;">
<table class="table table-sm table-striped mb-0">
<thead class="table-light sticky-top">
<tr>
<th style="width: 70px;">Type</th>
<th style="width: 150px;">Timestamp</th>
<th>Remarks</th>
</tr>
</thead>
<tbody id="tbl_complaint_details"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...@@ -3,15 +3,18 @@ unit View.ComplaintDetails; ...@@ -3,15 +3,18 @@ unit View.ComplaintDetails;
interface interface
uses uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls, System.SysUtils, System.Classes, JS, Web,
WEBLib.Forms, WEBLib.Dialogs; WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls,
WEBLib.Grids, WEBLib.DBCtrls;
type type
TFViewComplaintDetails = class(TWebForm) TFViewComplaintDetails = class(TWebForm)
WebDBTableControl1: TWebDBTableControl;
private private
{ Private declarations } FComplaintId: string;
public public
{ Public declarations } class function CreateForm(AElementID, ComplaintId: string): TWebForm;
procedure InitializeForm;
end; end;
var var
...@@ -21,4 +24,170 @@ implementation ...@@ -21,4 +24,170 @@ implementation
{$R *.dfm} {$R *.dfm}
end. class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string): TWebForm;
\ No newline at end of file
procedure AfterCreate(AForm: TObject);
begin
with TFViewComplaintDetails(AForm) do
begin
FComplaintId := ComplaintId;
InitializeForm; // kick off loading / UI binding here
end;
end;
begin
Application.CreateForm(TFViewComplaintDetails, AElementID, Result, @AfterCreate);
end;
procedure TFViewComplaintDetails.InitializeForm;
begin
// TODO:
// - call your XData endpoint with FComplaintId
// - bind fields / populate controls
// - handle spinner / errors as you do elsewhere
end;
end.
//unit View.ComplaintDetails;
//
//interface
//
//uses
// System.SysUtils, System.Classes, JS, Web,
// WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
// WEBLib.StdCtrls, WEBLib.WebCtrls, WEBLib.ExtCtrls,
// WEBLib.DB, Data.DB,
// XData.Web.Client, XData.Web.JsonDataset, XData.Web.Dataset,
// View.Main, View.Complaints, Vcl.Controls, WEBLib.Grids, WEBLib.DBCtrls;
//
//type
// TFViewComplaintDetails = class(TWebForm)
// // Header controls (ElementID -> HTML ids in snake_case)
// btnCloseComplaintDetails: TWebButton; // ElementID = 'btn_close_complaint_details'
// lblComplaintNumber: TWebLabel; // 'lbl_complaint_number'
// lblPriority: TWebLabel; // 'lbl_priority'
// lblStatus: TWebLabel; // 'lbl_status'
// lblDispatchCode: TWebLabel; // 'lbl_dispatch_code'
// lblDispatchDistrict: TWebLabel; // 'lbl_dispatch_district'
// lblAddress: TWebLabel; // 'lbl_address'
//
// // Action buttons (optional events later)
// btnHistory: TWebButton; // 'btn_history'
// btnWarnings: TWebButton; // 'btn_warnings'
// btnContacts: TWebButton; // 'btn_contacts'
// btnCMP: TWebButton; // 'btn_cmp'
// btnE911: TWebButton; // 'btn_e911'
// btnREM: TWebButton; // 'btn_rem'
// btnUNT: TWebButton; // 'btn_unt'
//
// // Data-aware table
// tblComplaintDetails: TWebDBListControl; // ElementID = 'tbl_complaint_details'
// xdwcComplaintDetails: TXDataWebClient;
// xdwdsxComplaintDetails: TXDataWebDataSet; // (your requested name)
// wdsComplaintDetails: TWebDataSource;
//
// private
// FComplaintId: string;
//
// [async] procedure LoadHeaderAsync;
// [async] procedure LoadRowsAsync;
//
// public
// class function CreateForm(AElementID, ComplaintId: string): TWebForm;
// procedure InitializeForm;
// end;
//
//implementation
//
//{$R *.dfm}
//
//class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string): TWebForm;
//
// procedure AfterCreate(AForm: TObject);
// begin
// with TFViewComplaintDetails(AForm) do
// begin
// FComplaintId := ComplaintId;
// InitializeForm;
// 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.
...@@ -85,20 +85,19 @@ object FViewComplaints: TFViewComplaints ...@@ -85,20 +85,19 @@ object FViewComplaints: TFViewComplaints
Style = lsListGroup Style = lsListGroup
DataSource = wdsComplaints DataSource = wdsComplaints
ItemTemplate = ItemTemplate =
'<div class="list-section-header small fw-semibold bg-secondary ' + '<div class="list-section-header small fw-semibold bg-secondary t' +
'text-white rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><div ' + 'ext-white rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><div cl' +
'class="card border shadow-sm" style=" --bs-card-bg: (%Priori' + 'ass="card border shadow-sm" style="--bs-card-bg:(%PriorityCo' +
'tyColor%); --bs-card-color: (%PriorityTextColor%); "> <div ' + 'lor%);--bs-card-color:(%PriorityTextColor%);"> <div class="card' +
'class="card-body py-2 px-3"> <div class="fw-bold text-upperca' + '-body py-2 px-3"> <div class="fw-bold text-uppercase small"> ' +
'se small"> (%Priority%): (%DispatchCodeDesc%) </div> ' + ' (%Priority%): (%DispatchCodeDesc%) </div> <div class=' +
'<div class="small">(%Address%)</div> <div class="small text-o' + '"small">(%Address%)</div> <div class="small text-opacity-75 d' +
'pacity-75"> (%Complaint%): (%Status%)&nbsp;&nbsp;(%District' + '-flex align-items-center"> <span> (%Complaint%): (%S' +
'Sector%) </div> <div class="small text-opacity-75">(%DateR' + 'tatus%)&nbsp;&nbsp;(%DistrictSector%) </span> <button ' +
'eported%)</div> <div class="d-flex justify-content-end mt-2' + 'type="button" class="btn btn-primary btn-sm ms-auto' +
'"> <button type="button" class="btn btn-li' + ' complaint-details-btn" data-id="(%ComplaintId%)"> ' +
'ght btn-sm complaint-details-btn" data-id="(%Comp' + ' Details </button> </div> <div class="small tex' +
'laintId%)"> Details </button> </div> </div' + 't-opacity-75">(%DateReported%)</div> </div></div>'
'></div>'
ListSource = wdsComplaints ListSource = wdsComplaints
end end
object xdwcComplaints: TXDataWebClient object xdwcComplaints: TXDataWebClient
......
<div class="sticky-top"> <div class="sticky-top">
<!-- Local navbar (Complaints) --> <!-- Local navbar (Complaints) -->
<nav class="navbar navbar-dark bg-primary py-2"><!-- removed sticky-top --> <nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid"> <div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch"> <div class="row w-100 g-2 align-items-stretch">
<div class="col"> <div class="col">
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</nav> </nav>
<!-- Search bar under local navbar --> <!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2"><!-- removed sticky-top --> <div class="bg-light border-bottom py-2">
<div class="container-fluid"> <div class="container-fluid">
<div class="input-group"> <div class="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span> <span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
</div> </div>
</div> </div>
</div> <!-- /sticky-top wrapper --> </div>
<!-- Complaints list container --> <!-- Complaints list container -->
<div class="container-fluid mt-2"> <div class="container-fluid mt-2">
...@@ -47,7 +47,6 @@ ...@@ -47,7 +47,6 @@
<!-- Cards will render here --> <!-- Cards will render here -->
</div> </div>
<!-- Entry Count Label -->
<label id="complaints_lblentries" class="mt-2 d-block"></label> <label id="complaints_lblentries" class="mt-2 d-block"></label>
<!-- Pagination --> <!-- Pagination -->
...@@ -60,3 +59,4 @@ ...@@ -60,3 +59,4 @@
</div> </div>
</div> </div>
...@@ -40,11 +40,12 @@ type ...@@ -40,11 +40,12 @@ type
procedure tmrRefreshTimer(Sender: TObject); procedure tmrRefreshTimer(Sender: TObject);
procedure WebFormDestroy(Sender: TObject); procedure WebFormDestroy(Sender: TObject);
private private
FSelectProc: TSelectProc;
FLoading: Boolean; FLoading: Boolean;
[async] procedure GetComplaints; [async] procedure GetComplaints;
procedure HandleListClick(e: TJSMouseEvent); procedure HandleListClick(e: TJSMouseEvent);
public public
property OnShowDetails: TSelectProc read FSelectProc write FSelectProc;
end; end;
var var
...@@ -72,10 +73,14 @@ begin ...@@ -72,10 +73,14 @@ begin
id := string(TJSHtmlElement(el).dataset['id']); // comes from (%ComplaintId%) id := string(TJSHtmlElement(el).dataset['id']); // comes from (%ComplaintId%)
e.preventDefault; e.preventDefault;
e.stopPropagation; e.stopPropagation;
if Assigned(FSelectProc) then
FSelectProc(id);
end; end;
end; end;
procedure TFViewComplaints.WebFormDestroy(Sender: TObject); procedure TFViewComplaints.WebFormDestroy(Sender: TObject);
begin begin
Document.removeEventListener('click', @HandleListClick); Document.removeEventListener('click', @HandleListClick);
......
// Form that functions as both a way to edit or add users to the database
// Author: Cameron Hayes
unit View.EditUser; unit View.EditUser;
interface interface
......
...@@ -52,6 +52,7 @@ type ...@@ -52,6 +52,7 @@ type
procedure ShowForm( AFormClass: TWebFormClass ); procedure ShowForm( AFormClass: TWebFormClass );
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);
end; end;
var var
...@@ -65,6 +66,7 @@ uses ...@@ -65,6 +66,7 @@ uses
View.UserProfile, View.UserProfile,
View.Map, View.Map,
View.Complaints, View.Complaints,
View.ComplaintDetails,
View.Units, View.Units,
View.Admin, View.Admin,
View.Users, View.Users,
...@@ -136,6 +138,13 @@ procedure TFViewMain.btnComplaintsClick(Sender: TObject); ...@@ -136,6 +138,13 @@ procedure TFViewMain.btnComplaintsClick(Sender: TObject);
begin begin
SetActiveNavButton('view.main.btncomplaints'); SetActiveNavButton('view.main.btncomplaints');
ShowForm(TFViewComplaints); ShowForm(TFViewComplaints);
if (FChildForm is TFViewComplaints) then
TFViewComplaints(FChildForm).OnShowDetails :=
procedure(AComplaintId: string)
begin
ShowComplaintDetails(AComplaintId);
end;
end; end;
procedure TFViewMain.btnMapClick(Sender: TObject); procedure TFViewMain.btnMapClick(Sender: TObject);
...@@ -186,6 +195,14 @@ begin ...@@ -186,6 +195,14 @@ begin
FChildForm := TFViewUsers.CreateForm(WebPanel1.ElementID, Info); FChildForm := TFViewUsers.CreateForm(WebPanel1.ElementID, Info);
end; end;
procedure TFViewMain.ShowComplaintDetails(ComplaintId: string);
begin
if Assigned(FChildForm) then
FChildForm.Free;
FChildForm := TFViewComplaintDetails.CreateForm(WebPanel1.ElementID, ComplaintId);
end;
procedure TFViewMain.SetActiveNavButton(const btnId: string); procedure TFViewMain.SetActiveNavButton(const btnId: string);
var var
......
...@@ -39,23 +39,22 @@ object FViewUnits: TFViewUnits ...@@ -39,23 +39,22 @@ object FViewUnits: TFViewUnits
Style = lsListGroup Style = lsListGroup
DataSource = wdsUnits DataSource = wdsUnits
ItemTemplate = ItemTemplate =
'<div class="list-section-header small fw-semibold bg-body-secon' + '<div class="list-section-header small fw-semibold bg-body-second' +
'dary text-dark rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><d' + 'ary text-dark rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><di' +
'iv class="card border shadow-sm position-relative"> <div class=' + 'v class="card border shadow-sm position-relative"> <div class="' +
'"card-body py-2 px-3"> <!-- Unit + Status --> <div class="' + 'card-body py-2 px-3"> <!-- Unit + Status --> <div class="f' +
'fw-bold text-uppercase small"> (%UnitName%)&nbsp;-&nbsp;(%S' + 'w-bold text-uppercase small"> (%UnitName%)&nbsp;-&nbsp;(%St' +
'tatus%) </div> <!-- Location --> <div class="small text' + 'atus%) </div> <!-- Location --> <div class="small text-' +
'-body-secondary mb-1"> (%Location%) </div> <!-- Call ' + 'body-secondary mb-1"> (%Location%) </div> <!-- Call t' +
'type --> <div class="small">(%CallType%)</div> <!-- Divide' + 'ype --> <div class="small">(%CallType%)</div> <!-- Divider' +
'r line (we'#39'll auto-hide if no officers) --> <hr class="unit-d' + ' line --> <hr class="unit-divider my-1" style="width:80px;mar' +
'ivider my-1" style="width: 80px; margin-left: 0;"> <!-- Offic' + 'gin-left:0;"> <!-- Officers --> <div class="small officer1' +
'ers --> <div class="small officer1">(%Officer1%)</div> <di' + '">(%Officer1%)</div> <div class="small officer2">(%Officer2%)' +
'v class="small officer2">(%Officer2%)</div> </div> <!-- Floati' + '</div> </div> <!-- Vertically centered Details button on the r' +
'ng details button (solid, info icon) --> <div class="position-a' + 'ight --> <div class="position-absolute top-50 end-0 translate-m' +
'bsolute top-50 end-0 translate-middle-y pe-2"> <button class=' + 'iddle-y pe-2"> <button type="button" class="btn bt' +
'"btn btn-secondary btn-sm btn-unit-details" data-unit' + 'n-primary btn-sm btn-unit-details" data-unitid="(%Uni' +
'id="(%UnitId%)"> <i class="fa fa-info"></i> </button> <' + 'tId%)"> Details </button> </div></div>'
'/div></div>'
ListSource = wdsUnits ListSource = wdsUnits
end end
object btnRefresh: TWebButton object btnRefresh: TWebButton
......
...@@ -24,60 +24,7 @@ ...@@ -24,60 +24,7 @@
display: table-cell display: table-cell
} }
.table tbody tr {
transition: background-color 0.3s ease;
}
.table {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
@media (max-width: 1200px) {
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.table thead {
display: none;
}
.table tbody, .table tr, .table td {
display: block;
width: 100%;
}
.table tr {
margin-bottom: 1rem;
}
.table td {
text-align: right;
padding-left: 50%; /* Adjust padding to accommodate the data-label */
position: relative;
}
.table td::before {
content: attr(data-label);
position: absolute;
left: 0;
width: 50%;
padding-left: 15px; /* Adjust as necessary */
font-weight: bold;
text-align: left;
}
.table td .transcript {
margin-top: 20px; /* Set top margin to 20px */
text-align: left; /* Ensure text alignment is left */
margin-left: 8px;
white-space: normal; /* Prevent text from being cut off */
}
}
.login-navbar { .login-navbar {
...@@ -159,3 +106,5 @@ span.card { ...@@ -159,3 +106,5 @@ span.card {
program webEmiMobile; program webEmiMobile;
{$R *.dres}
uses uses
Vcl.Forms, Vcl.Forms,
XData.Web.Connection, XData.Web.Connection,
......
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