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
Height = 491
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
Left = 182
Top = 98
......@@ -632,7 +613,8 @@ object ApiDatabaseModule: TApiDatabaseModule
' ).sdo_point.y'
' 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'
'JOIN COMPLAINT_TIMES ct'
' ON ct.COMPLAINTID = ca.COMPLAINTID'
......@@ -675,6 +657,10 @@ object ApiDatabaseModule: TApiDatabaseModule
ReadOnly = True
Size = 50
end
object uqMapComplaintsADDRESS: TStringField
FieldName = 'ADDRESS'
Size = 64
end
object uqMapComplaintspriorityKey: TStringField
FieldKind = fkCalculated
FieldName = 'priorityKey'
......@@ -711,4 +697,54 @@ object ApiDatabaseModule: TApiDatabaseModule
ReadOnly = True
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
......@@ -9,9 +9,6 @@ uses
type
TApiDatabaseModule = class(TDataModule)
ucEnvoy: TUniConnection;
PostgreSQLUniProvider1: TPostgreSQLUniProvider;
UniQuery1: TUniQuery;
OracleUniProvider1: TOracleUniProvider;
uqBooking: TUniQuery;
uqMapUnits: TUniQuery;
......@@ -121,6 +118,16 @@ type
uqComplaintDetailsDATERESPONDED: TDateTimeField;
uqComplaintDetailsDATEARRIVED: 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 uqMapComplaintsCalcFields(DataSet: TDataSet);
private
......@@ -129,7 +136,6 @@ type
function DerivePriorityKeyFromPriorityString(const priorityString: string): string;
function HandleUniqueFilenames(const category: string): string;
function BadgeCounts(const BaseQuery: TUniQuery): Integer;
class procedure ExecSQL(const SQL: string);
end;
var
......@@ -141,19 +147,6 @@ implementation
{$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);
var
raw: string;
......
......@@ -57,7 +57,6 @@ begin
begin
Open;
try
// NOTE: your TUniQuery exposes fields named COMPLAINTS and UNITS
Result.AddPair('BadgeComplaints', TJSONNumber.Create(FieldByName('COMPLAINTS').AsInteger));
Result.AddPair('BadgeUnits', TJSONNumber.Create(FieldByName('UNITS').AsInteger));
finally
......@@ -80,60 +79,115 @@ var
data: TJSONArray;
emitted: Integer;
item: TJSONObject;
UnitsByComplaintMap: TObjectDictionary<string,TJSONArray>;
complaintId: string;
unitArray: TJSONArray;
unitStatus: string;
latestUpdate: TDateTime;
unitObj: TJSONObject;
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);
data := TJSONArray.Create;
data:=TJSONArray.Create;
UnitsByComplaintMap:=TObjectDictionary<string,TJSONArray>.Create([doOwnsValues]);
try
emitted := 0;
with ApiDB.uqMapComplaints do
ApiDB.uqMapComplaintUnitsList.Open;
while not ApiDB.uqMapComplaintUnitsList.Eof do
begin
Open;
First;
while not Eof do
complaintId:=ApiDB.uqMapComplaintUnitsListCOMPLAINTID.AsString;
if not UnitsByComplaintMap.TryGetValue(complaintId,unitArray) then
begin
if ApiDB.uqMapComplaintsLAT.IsNull or ApiDB.uqMapComplaintsLNG.IsNull then
begin
Next;
Continue;
end;
item := TJSONObject.Create;
unitArray:=TJSONArray.Create;
UnitsByComplaintMap.Add(complaintId,unitArray);
end;
item.AddPair('ComplaintId', ApiDB.uqMapComplaintsCOMPLAINTID.AsString);
item.AddPair('DispatchDistrict', ApiDB.uqMapComplaintsDISPATCHDISTRICT.AsString);
item.AddPair('DispatchCodeDesc', ApiDB.uqMapComplaintsDISPATCH_CODE_DESC.AsString);
unitStatus:='Dispatched';
if not ApiDB.uqMapComplaintUnitsListDATECLEARED.IsNull then
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);
item.AddPair('Priority', ApiDB.uqMapComplaintsPRIORITY.AsString);
item.AddPair('priorityKey', ApiDB.uqMapComplaints.FieldByName('priorityKey').AsString);
item.AddPair('pngName', ApiDB.uqMapComplaints.FieldByName('pngName').AsString);
try
emitted:=0;
ApiDB.uqMapComplaints.Open;
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('Lng', TJSONNumber.Create(ApiDB.uqMapComplaintsLNG.AsFloat));
item.AddPair('Lat',TJSONNumber.Create(ApiDB.uqMapComplaintsLAT.AsFloat));
item.AddPair('Lng',TJSONNumber.Create(ApiDB.uqMapComplaintsLNG.AsFloat));
data.AddElement(item);
Inc(emitted);
Next;
ApiDB.uqMapComplaints.Next;
end;
end;
Result.AddPair('count', TJSONNumber.Create(data.Count));
Result.AddPair('returned', TJSONNumber.Create(emitted));
Result.AddPair('data', data);
except
data.Free;
Logger.Log(3, '---TApiService.GetComplaintMap End (error)');
raise EXDataHttpException.Create(500, 'Failed to load complaint map');
Result.AddPair('count',TJSONNumber.Create(data.Count));
Result.AddPair('returned',TJSONNumber.Create(emitted));
Result.AddPair('data',data);
Logger.Log(3,'---TApiService.GetComplaintMap End (returned='+emitted.ToString+')');
except
on E: Exception do
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;
Logger.Log(3, '---TApiService.GetComplaintMap End');
end;
function TApiService.GetUnitMap: TJSONObject;
var
data: TJSONArray;
......@@ -211,7 +265,6 @@ begin
var item := TJSONObject.Create;
// Add a section header only when the district changes
var curDistrict := ApiDB.uqComplaintListDISPATCHDISTRICT.AsString;
if not SameText(curDistrict, lastDistrict) then
item.AddPair('DistrictHeader', curDistrict);
......@@ -220,11 +273,9 @@ begin
var districtSector := ApiDB.uqComplaintListDISTRICT_DESC.AsString + ApiDB.uqComplaintListSECTOR_DESC.AsString;
item.AddPair('DistrictSector', districtSector);
// existing color hex
var colorVal := ApiDB.uqComplaintListPRIORITY_COLOR.AsInteger;
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
item.AddPair('PriorityTextColor', '#FFFFFF')
else
......
......@@ -51,10 +51,6 @@ object AuthDatabase: TAuthDatabase
Left = 249
Top = 45
end
object PostgreSQLUniProvider1: TPostgreSQLUniProvider
Left = 276
Top = 156
end
object OracleUniProvider1: TOracleUniProvider
Left = 94
Top = 152
......@@ -78,7 +74,7 @@ object AuthDatabase: TAuthDatabase
end
object uqBooking: TUniQuery
Connection = ucBooking
Left = 98
Top = 44
Left = 88
Top = 48
end
end
......@@ -10,7 +10,6 @@ type
TAuthDatabase = class(TDataModule)
uq: TUniQuery;
uqMisc: TUniQuery;
PostgreSQLUniProvider1: TPostgreSQLUniProvider;
uquser_id: TLargeintField;
uqusername: TStringField;
uqpassword: TMemoField;
......@@ -70,21 +69,6 @@ begin
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
iniFile.Free;
end;
......
......@@ -15,30 +15,11 @@ type
FFileLogLevelFromIni: Boolean;
FLogFileNum: Integer;
FLogFileNumFromIni: Boolean;
FEmailPolling: Boolean;
FEmailPollingFromIni: Boolean;
FEmailPollingInterval: Integer;
FEmailPollingIntervalFromIni: Boolean;
FWebClientVersion: string;
FWebClientVersionFromIni: Boolean;
FTwilioUpdateTime: Integer;
FTwilioUpdateTimeFromIni: Boolean;
// [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
constructor Create;
......@@ -49,30 +30,12 @@ type
property FileLogLevelFromIni: Boolean read FFileLogLevelFromIni;
property LogFileNum: Integer read FLogFileNum;
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 WebClientVersionFromIni: Boolean read FWebClientVersionFromIni;
property TwilioUpdateTime: Integer read FTwilioUpdateTime;
property TwilioUpdateTimeFromIni: Boolean read FTwilioUpdateTimeFromIni;
// [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;
procedure LoadIniEntries;
......@@ -107,37 +70,11 @@ begin
FLogFileNum := iniFile.ReadInteger('Settings', 'LogFileNum', 0);
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', '');
FWebClientVersionFromIni := iniFile.ValueExists('Settings', 'webClientVersion');
FTwilioUpdateTime := iniFile.ReadInteger('Settings', 'TwilioUpdateTime', 0);
FTwilioUpdateTimeFromIni := iniFile.ValueExists('Settings', 'TwilioUpdateTime');
// [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
iniFile.Free;
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
TextHeight = 13
object memoInfo: TMemo
Left = 8
Top = 40
Top = 39
Width = 744
Height = 549
Anchors = [akLeft, akTop, akRight, akBottom]
......@@ -33,22 +33,13 @@ object FMain: TFMain
TabOrder = 1
OnClick = btnApiSwaggerUIClick
end
object btnData: TButton
Left = 525
Top = 8
Width = 75
Height = 25
Caption = 'Data'
TabOrder = 2
OnClick = btnDataClick
end
object btnExit: TButton
Left = 671
Top = 8
Width = 75
Height = 25
Caption = 'Exit'
TabOrder = 3
TabOrder = 2
OnClick = btnExitClick
end
object btnAuthSwaggerUI: TButton
......@@ -57,7 +48,7 @@ object FMain: TFMain
Width = 100
Height = 25
Caption = 'Auth SwaggerUI'
TabOrder = 4
TabOrder = 3
OnClick = btnAuthSwaggerUIClick
end
object initTimer: TTimer
......
......@@ -13,20 +13,17 @@ type
TFMain = class(TForm)
memoInfo: TMemo;
btnApiSwaggerUI: TButton;
btnData: TButton;
btnExit: TButton;
initTimer: TTimer;
btnAuthSwaggerUI: TButton;
ExeInfo1: TExeInfo;
procedure btnApiSwaggerUIClick(Sender: TObject);
procedure btnDataClick(Sender: TObject);
procedure btnExitClick(Sender: TObject);
procedure ContactFormData(AText: String);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure initTimerTimer(Sender: TObject);
procedure btnAuthSwaggerUIClick(Sender: TObject);
strict private
phoneDict: TDictionary<string, string>;
procedure StartServers;
function LogValue(const LabelName: string; const Value: string; FromIni: Boolean): string;
end;
......@@ -41,7 +38,8 @@ uses
Common.Config,
Common.Ini,
Sparkle.Utils,
Data, Twilio.Data.Module, Api.Database, System.StrUtils;
Api.Database,
System.StrUtils;
{$R *.dfm}
......@@ -62,14 +60,6 @@ begin
ShellExecute(Handle, 'open', PChar(TSparkleUtils.CombineUrlFast(AuthServerModule.XDataServer.BaseUrl, 'swaggerui')), nil, nil, SW_SHOWNORMAL);
end;
procedure TFMain.btnDataClick(Sender: TObject);
begin
FData := TFData.Create(Self);
FData.ShowModal;
FData.Free;
end;
procedure TFMain.ContactFormData(AText: String);
begin
if memoInfo.CanFocus then
......@@ -90,7 +80,6 @@ end;
procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
phoneDict.Free;
ServerConfig.Free;
IniEntries.Free;
AuthServerModule.Free;
......@@ -118,16 +107,15 @@ begin
Logger.Log(1, '--- Settings ---');
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->TwilioUpdateTime', IniEntries.TwilioUpdateTime.ToString, IniEntries.TwilioUpdateTimeFromIni));
Logger.Log(1, '');
Logger.Log(1, '--- Database ---');
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->Username', IniEntries.DatabaseUsername, IniEntries.DatabaseUsernameFromIni));
Logger.Log(1, LogValue('--Database->Password', IniEntries.DatabasePassword, IniEntries.DatabasePasswordFromIni));
Logger.Log(1, '');
// Logger.Log(1, '--- Database ---');
// 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->Username', IniEntries.DatabaseUsername, IniEntries.DatabaseUsernameFromIni));
// Logger.Log(1, LogValue('--Database->Password', IniEntries.DatabasePassword, IniEntries.DatabasePasswordFromIni));
Logger.Log(1, '');
Logger.Log(1, '--- URLs ---');
try
AuthServerModule := TAuthServerModule.Create(Self);
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
Api.Server.Module in 'Source\Api.Server.Module.pas' {ApiServerModule: TDataModule},
Main in 'Source\Main.pas' {FMain},
Common.Logging in 'Source\Common.Logging.pas',
Data in 'Source\Data.pas' {FData},
Api.Database in 'Source\Api.Database.pas' {ApiDatabaseModule: TDataModule},
Common.Middleware.Logging in 'Source\Common.Middleware.Logging.pas',
Common.Config in 'Source\Common.Config.pas',
......@@ -21,7 +20,6 @@ uses
Api.Service in 'Source\Api.Service.pas',
Auth.ServiceImpl in 'Source\Auth.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},
Common.Ini in 'Source\Common.Ini.pas';
......
......@@ -141,9 +141,6 @@
<Form>FMain</Form>
</DCCReference>
<DCCReference Include="Source\Common.Logging.pas"/>
<DCCReference Include="Source\Data.pas">
<Form>FData</Form>
</DCCReference>
<DCCReference Include="Source\Api.Database.pas">
<Form>ApiDatabaseModule</Form>
<DesignClass>TDataModule</DesignClass>
......@@ -163,10 +160,6 @@
<DCCReference Include="Source\Api.Service.pas"/>
<DCCReference Include="Source\Auth.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">
<Form>AppServerModule</Form>
<DesignClass>TDataModule</DesignClass>
......
[Settings]
LogFileNum=513
LogFileNum=525
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
Width = 640
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
<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
<!-- Sticky local navbar (Complaint Details) -->
<div class="sticky-top">
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<div id="cdetails_title" class="navbar-brand mb-0 h5 text-white">Complaint Details</div>
</div>
<div class="col-auto">
<button id="btn_close_complaint_details" type="button" class="btn btn-outline-light w-100 h-100">
<i class="fa fa-times me-1"></i>
<span class="d-none d-sm-inline">Close</span>
</button>
</div>
</div>
</div>
</nav>
</div>
<!-- Complaint details content -->
<div class="container-fluid mt-2 complaint-details-page">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<!-- Outer card -->
<div class="card border-0 shadow-sm">
<div class="card-body p-3 bg-light">
<!-- Summary panel -->
<div class="card mb-3">
<div class="card-body py-2">
<div class="row gy-1">
<div class="col-5 col-sm-4 fw-semibold">Complaint</div>
<div class="col-7 col-sm-8" id="lbl_complaint_number"></div>
<div class="col-5 col-sm-4 fw-semibold">Priority</div>
<div class="col-7 col-sm-8" id="lbl_priority"></div>
<div class="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;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs;
System.SysUtils, System.Classes, JS, Web,
WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls,
WEBLib.Grids, WEBLib.DBCtrls;
type
TFViewComplaintDetails = class(TWebForm)
WebDBTableControl1: TWebDBTableControl;
private
{ Private declarations }
FComplaintId: string;
public
{ Public declarations }
class function CreateForm(AElementID, ComplaintId: string): TWebForm;
procedure InitializeForm;
end;
var
......@@ -21,4 +24,170 @@ implementation
{$R *.dfm}
end.
\ No newline at end of file
class function TFViewComplaintDetails.CreateForm(AElementID, ComplaintId: string): TWebForm;
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
Style = lsListGroup
DataSource = wdsComplaints
ItemTemplate =
'<div class="list-section-header small fw-semibold bg-secondary ' +
'text-white rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><div ' +
'class="card border shadow-sm" style=" --bs-card-bg: (%Priori' +
'tyColor%); --bs-card-color: (%PriorityTextColor%); "> <div ' +
'class="card-body py-2 px-3"> <div class="fw-bold text-upperca' +
'se small"> (%Priority%): (%DispatchCodeDesc%) </div> ' +
'<div class="small">(%Address%)</div> <div class="small text-o' +
'pacity-75"> (%Complaint%): (%Status%)&nbsp;&nbsp;(%District' +
'Sector%) </div> <div class="small text-opacity-75">(%DateR' +
'eported%)</div> <div class="d-flex justify-content-end mt-2' +
'"> <button type="button" class="btn btn-li' +
'ght btn-sm complaint-details-btn" data-id="(%Comp' +
'laintId%)"> Details </button> </div> </div' +
'></div>'
'<div class="list-section-header small fw-semibold bg-secondary t' +
'ext-white rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><div cl' +
'ass="card border shadow-sm" style="--bs-card-bg:(%PriorityCo' +
'lor%);--bs-card-color:(%PriorityTextColor%);"> <div class="card' +
'-body py-2 px-3"> <div class="fw-bold text-uppercase small"> ' +
' (%Priority%): (%DispatchCodeDesc%) </div> <div class=' +
'"small">(%Address%)</div> <div class="small text-opacity-75 d' +
'-flex align-items-center"> <span> (%Complaint%): (%S' +
'tatus%)&nbsp;&nbsp;(%DistrictSector%) </span> <button ' +
'type="button" class="btn btn-primary btn-sm ms-auto' +
' complaint-details-btn" data-id="(%ComplaintId%)"> ' +
' Details </button> </div> <div class="small tex' +
't-opacity-75">(%DateReported%)</div> </div></div>'
ListSource = wdsComplaints
end
object xdwcComplaints: TXDataWebClient
......
<div class="sticky-top">
<!-- 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="row w-100 g-2 align-items-stretch">
<div class="col">
......@@ -27,7 +27,7 @@
</nav>
<!-- 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="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
......@@ -36,7 +36,7 @@
</div>
</div>
</div> <!-- /sticky-top wrapper -->
</div>
<!-- Complaints list container -->
<div class="container-fluid mt-2">
......@@ -47,7 +47,6 @@
<!-- Cards will render here -->
</div>
<!-- Entry Count Label -->
<label id="complaints_lblentries" class="mt-2 d-block"></label>
<!-- Pagination -->
......@@ -60,3 +59,4 @@
</div>
</div>
......@@ -40,11 +40,12 @@ type
procedure tmrRefreshTimer(Sender: TObject);
procedure WebFormDestroy(Sender: TObject);
private
FSelectProc: TSelectProc;
FLoading: Boolean;
[async] procedure GetComplaints;
procedure HandleListClick(e: TJSMouseEvent);
public
property OnShowDetails: TSelectProc read FSelectProc write FSelectProc;
end;
var
......@@ -72,10 +73,14 @@ begin
id := string(TJSHtmlElement(el).dataset['id']); // comes from (%ComplaintId%)
e.preventDefault;
e.stopPropagation;
if Assigned(FSelectProc) then
FSelectProc(id);
end;
end;
procedure TFViewComplaints.WebFormDestroy(Sender: TObject);
begin
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;
interface
......
......@@ -52,6 +52,7 @@ type
procedure ShowForm( AFormClass: TWebFormClass );
procedure EditUser( Mode, FullName, Username, Phone, Email: string; admin, active: boolean);
procedure ShowUserForm(Info: string);
procedure ShowComplaintDetails(ComplaintId: string);
end;
var
......@@ -65,6 +66,7 @@ uses
View.UserProfile,
View.Map,
View.Complaints,
View.ComplaintDetails,
View.Units,
View.Admin,
View.Users,
......@@ -136,6 +138,13 @@ procedure TFViewMain.btnComplaintsClick(Sender: TObject);
begin
SetActiveNavButton('view.main.btncomplaints');
ShowForm(TFViewComplaints);
if (FChildForm is TFViewComplaints) then
TFViewComplaints(FChildForm).OnShowDetails :=
procedure(AComplaintId: string)
begin
ShowComplaintDetails(AComplaintId);
end;
end;
procedure TFViewMain.btnMapClick(Sender: TObject);
......@@ -186,6 +195,14 @@ begin
FChildForm := TFViewUsers.CreateForm(WebPanel1.ElementID, Info);
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);
var
......
......@@ -39,23 +39,22 @@ object FViewUnits: TFViewUnits
Style = lsListGroup
DataSource = wdsUnits
ItemTemplate =
'<div class="list-section-header small fw-semibold bg-body-secon' +
'dary text-dark rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><d' +
'iv class="card border shadow-sm position-relative"> <div class=' +
'"card-body py-2 px-3"> <!-- Unit + Status --> <div class="' +
'fw-bold text-uppercase small"> (%UnitName%)&nbsp;-&nbsp;(%S' +
'tatus%) </div> <!-- Location --> <div class="small text' +
'-body-secondary mb-1"> (%Location%) </div> <!-- Call ' +
'type --> <div class="small">(%CallType%)</div> <!-- Divide' +
'r line (we'#39'll auto-hide if no officers) --> <hr class="unit-d' +
'ivider my-1" style="width: 80px; margin-left: 0;"> <!-- Offic' +
'ers --> <div class="small officer1">(%Officer1%)</div> <di' +
'v class="small officer2">(%Officer2%)</div> </div> <!-- Floati' +
'ng details button (solid, info icon) --> <div class="position-a' +
'bsolute top-50 end-0 translate-middle-y pe-2"> <button class=' +
'"btn btn-secondary btn-sm btn-unit-details" data-unit' +
'id="(%UnitId%)"> <i class="fa fa-info"></i> </button> <' +
'/div></div>'
'<div class="list-section-header small fw-semibold bg-body-second' +
'ary text-dark rounded-1 px-2 mb-1"> (%DistrictHeader%)</div><di' +
'v class="card border shadow-sm position-relative"> <div class="' +
'card-body py-2 px-3"> <!-- Unit + Status --> <div class="f' +
'w-bold text-uppercase small"> (%UnitName%)&nbsp;-&nbsp;(%St' +
'atus%) </div> <!-- Location --> <div class="small text-' +
'body-secondary mb-1"> (%Location%) </div> <!-- Call t' +
'ype --> <div class="small">(%CallType%)</div> <!-- Divider' +
' line --> <hr class="unit-divider my-1" style="width:80px;mar' +
'gin-left:0;"> <!-- Officers --> <div class="small officer1' +
'">(%Officer1%)</div> <div class="small officer2">(%Officer2%)' +
'</div> </div> <!-- Vertically centered Details button on the r' +
'ight --> <div class="position-absolute top-50 end-0 translate-m' +
'iddle-y pe-2"> <button type="button" class="btn bt' +
'n-primary btn-sm btn-unit-details" data-unitid="(%Uni' +
'tId%)"> Details </button> </div></div>'
ListSource = wdsUnits
end
object btnRefresh: TWebButton
......
......@@ -24,60 +24,7 @@
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 {
......@@ -159,3 +106,5 @@ span.card {
program webEmiMobile;
{$R *.dres}
uses
Vcl.Forms,
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