Commit cb5e8ce5 by Mac Stephens

Improve TasksHTML row management, header info, and layout scrolling; add…

Improve TasksHTML row management,  header info, and layout scrolling; add insert-below-selected row behavior end-to-end; implement scroll position preservation
parent 38ccc4e0
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<ProjectGuid>{F0FD637A-3831-4B55-80F8-F15510E0463F}</ProjectGuid> <ProjectGuid>{F0FD637A-3831-4B55-80F8-F15510E0463F}</ProjectGuid>
<ProjectVersion>20.3</ProjectVersion> <ProjectVersion>20.4</ProjectVersion>
<FrameworkType>VCL</FrameworkType> <FrameworkType>VCL</FrameworkType>
<MainSource>emT3VCLDemo.cpp</MainSource> <MainSource>emT3VCLDemo.cpp</MainSource>
<AppType>Application</AppType> <AppType>Application</AppType>
...@@ -254,6 +254,16 @@ ...@@ -254,6 +254,16 @@
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
</Platform> </Platform>
</DeployFile> </DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true'" LocalName="$(BDS)\bin64\libc++-370.dll" Class="DependencyModule">
<Platform Name="Win64x">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true'" LocalName="$(BDS)\bin64\libunwind-370.dll" Class="DependencyModule">
<Platform Name="Win64x">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(UsingDelphiRTL)'=='true'" LocalName="$(BDS)\bin\borlndmm.dll" Class="DependencyModule"> <DeployFile Condition="'$(UsingDelphiRTL)'=='true'" LocalName="$(BDS)\bin\borlndmm.dll" Class="DependencyModule">
<Platform Name="Win32"> <Platform Name="Win32">
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
...@@ -299,6 +309,21 @@ ...@@ -299,6 +309,21 @@
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
</Platform> </Platform>
</DeployFile> </DeployFile>
<DeployFile Condition="'$(UsingDelphiRTL)'=='true'" LocalName="$(BDS)\binarm64ec\borlndmm.dll" Class="DependencyModule">
<Platform Name="WinARM64EC">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'!='true'" LocalName="$(BDS)\binarm64ec\cc64370.dll" Class="DependencyModule">
<Platform Name="WinARM64EC">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile Condition="'$(DynamicRTL)'=='true' And '$(Multithreaded)'=='true'" LocalName="$(BDS)\binarm64ec\cc64370mt.dll" Class="DependencyModule">
<Platform Name="WinARM64EC">
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName=".\Win64x\Debug\emT3VCLDemo.exe" Configuration="Debug" Class="ProjectOutput"> <DeployFile LocalName=".\Win64x\Debug\emT3VCLDemo.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win64x"> <Platform Name="Win64x">
<RemoteName>emT3VCLDemo.exe</RemoteName> <RemoteName>emT3VCLDemo.exe</RemoteName>
...@@ -968,6 +993,9 @@ ...@@ -968,6 +993,9 @@
<Platform Name="Win64x"> <Platform Name="Win64x">
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
<Platform Name="WinARM64EC">
<Operation>1</Operation>
</Platform>
</DeployClass> </DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug"> <DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32"> <Platform Name="iOSDevice32">
...@@ -1042,6 +1070,10 @@ ...@@ -1042,6 +1070,10 @@
<RemoteDir>Assets</RemoteDir> <RemoteDir>Assets</RemoteDir>
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
<Platform Name="WinARM64EC">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass> </DeployClass>
<DeployClass Name="UWP_CppLogo44"> <DeployClass Name="UWP_CppLogo44">
<Platform Name="Win32"> <Platform Name="Win32">
...@@ -1056,6 +1088,10 @@ ...@@ -1056,6 +1088,10 @@
<RemoteDir>Assets</RemoteDir> <RemoteDir>Assets</RemoteDir>
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
<Platform Name="WinARM64EC">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass> </DeployClass>
<DeployClass Name="iOS_AppStore1024"> <DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64"> <Platform Name="iOSDevice64">
...@@ -1269,6 +1305,7 @@ ...@@ -1269,6 +1305,7 @@
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/> <ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/> <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/> <ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="WinARM64EC" Name="$(PROJECTNAME)"/>
</Deployment> </Deployment>
<Platforms> <Platforms>
<Platform value="Win32">True</Platform> <Platform value="Win32">True</Platform>
......
...@@ -106,7 +106,7 @@ object fMain: TfMain ...@@ -106,7 +106,7 @@ object fMain: TfMain
'MySQL.HttpTrustServerCertificate=False' 'MySQL.HttpTrustServerCertificate=False'
'MySQL.ProxyPort=0') 'MySQL.ProxyPort=0')
Username = 'root' Username = 'root'
Server = '192.168.102.129' Server = '192.168.102.131'
LoginPrompt = False LoginPrompt = False
Left = 390 Left = 390
Top = 342 Top = 342
......
...@@ -10,13 +10,9 @@ procedure HideStatusMessage(const AElementId: string); ...@@ -10,13 +10,9 @@ procedure HideStatusMessage(const AElementId: string);
procedure ShowSpinner(SpinnerID: string); procedure ShowSpinner(SpinnerID: string);
procedure HideSpinner(SpinnerID: string); procedure HideSpinner(SpinnerID: string);
procedure ShowErrorModal(msg: string); procedure ShowErrorModal(msg: string);
function CalculateAge(DateOfBirth: TDateTime): Integer;
function FormatPhoneNumber(PhoneNumber: string): string;
procedure ApplyReportTitle(CurrentReportType: string);
procedure ShowToast(const MessageText: string; const ToastType: string = 'success'); procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>); procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
procedure ShowNotificationModal(msg: string); procedure ShowNotificationModal(msg: string);
// function FormatDollarValue(ValueStr: string): string;
implementation implementation
...@@ -153,28 +149,6 @@ begin ...@@ -153,28 +149,6 @@ begin
end; end;
// ShowConfirmationModal displays a two-button modal with custom labels.
// Params:
// - messageText: text shown in the modal body
// - leftButtonText: label for the left button (e.g., "Cancel")
// - rightButtonText: label for the right button (e.g., "Delete")
// - callback: procedure(confirmed: Boolean); confirmed = True if right button clicked
//
// Example:
// ShowConfirmationModal('Delete this?', 'Cancel', 'Delete',
// procedure(confirmed: Boolean)
// begin
// if confirmed then DeleteOrder();
// end);
// function ShowConfirmationModal(msg, leftLabel, rightLabel: string;): Boolean;
// if ShowConfirmationModal then
// doThing()
// else
// doOtherThing();
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>); procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
begin begin
asm asm
...@@ -211,49 +185,6 @@ begin ...@@ -211,49 +185,6 @@ begin
end; end;
function CalculateAge(DateOfBirth: TDateTime): Integer;
var
Today, BirthDate: TJSDate;
Year, Month, Day, BirthYear, BirthMonth, BirthDay: NativeInt;
DOBString: string;
begin
Today := TJSDate.New;
Year := Today.FullYear;
Month := Today.Month + 1;
Day := Today.Date;
// Formats the DateOfBirth as an ISO 8601 date string
DOBString := FormatDateTime('yyyy-mm-dd', DateOfBirth);
BirthDate := TJSDate.New(DOBString);
if BirthDate = nil then
begin
Exit(0); // Exit the function with an age of 0 if the date creation fails
end;
BirthYear := BirthDate.FullYear;
BirthMonth := BirthDate.Month + 1;
BirthDay := BirthDate.Date;
Result := Year - BirthYear;
if (Month < BirthMonth) or ((Month = BirthMonth) and (Day < BirthDay)) then
Dec(Result);
end;
function FormatPhoneNumber(PhoneNumber: string): string;
var
Digits: string;
begin
Digits := PhoneNumber.Replace('(', '').Replace(')', '').Replace('-', '').Replace(' ', '');
case Length(Digits) of
7: Result := Format('%s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 4)]);
10: Result := Format('(%s) %s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 3), Copy(Digits, 7, 4)]);
else
// If the number does not have 7 or 10 digits, whatever they typed is returned
Result := PhoneNumber;
end;
end;
procedure ShowToast(const MessageText: string; const ToastType: string = 'success'); procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
var var
ParsedText, ToastKind, MsgPrefix: string; ParsedText, ToastKind, MsgPrefix: string;
...@@ -318,35 +249,5 @@ begin ...@@ -318,35 +249,5 @@ begin
end; end;
procedure ApplyReportTitle(CurrentReportType: string);
var
CrimeTitleElement: TJSHTMLElement;
begin
CrimeTitleElement := TJSHTMLElement(document.getElementById('crime_title'));
if Assigned(CrimeTitleElement) then
CrimeTitleElement.innerText := CurrentReportType
else
Console.Log('Element with ID "crime_title" not found.');
end;
// Used html number input type to restrict the input instead of this function
// function FormatDollarValue(ValueStr: string): string;
// var
// i: Integer;
// begin
// Result := ''; // Initialize the result
// // Filter out any characters that are not digits or decimal point
// for i := 1 to Length(ValueStr) do
// begin
// if (Pos(ValueStr[i], '0123456789.') > 0) then
// begin
// Result := Result + ValueStr[i];
// end;
// end;
// end;
end. end.
...@@ -98,27 +98,9 @@ object FViewMain: TFViewMain ...@@ -98,27 +98,9 @@ object FViewMain: TFViewMain
Role = 'null' Role = 'null'
TabOrder = 0 TabOrder = 0
end end
object memoDebug: TWebMemo
Left = 45
Top = 361
Width = 471
Height = 83
ElementID = 'memo_debug'
ElementPosition = epRelative
Enabled = False
HeightPercent = 100.000000000000000000
Lines.Strings = (
'WebMemo1')
Role = 'null'
SelLength = 0
SelStart = 0
ShowFocus = False
Visible = False
WidthPercent = 100.000000000000000000
end
object xdwcMain: TXDataWebClient object xdwcMain: TXDataWebClient
Connection = DMConnection.ApiConnection Connection = DMConnection.ApiConnection
Left = 80 Left = 76
Top = 476 Top = 332
end end
end end
...@@ -56,16 +56,8 @@ ...@@ -56,16 +56,8 @@
</div> </div>
<!-- Main Panel (where all forms display) --> <!-- Main Panel (where all forms display) -->
<div class="container-fluid py-3"> <div class="container-fluid py-3 d-flex flex-column overflow-hidden" style="height: calc(100vh - 57px);">
<div class="row"> <div id="pnl_main" class="flex-grow-1 min-h-0 overflow-hidden"></div>
<div id="pnl_main" class="col-12"></div>
</div>
<div class="row mt-3">
<div class="col-12">
<textarea class="form-control font-monospace" id="memo_debug" rows="4" placeholder="Debug output..."></textarea>
</div>
</div>
</div> </div>
<!-- Spinner Modal --> <!-- Spinner Modal -->
......
...@@ -17,7 +17,6 @@ type ...@@ -17,7 +17,6 @@ type
lblLogout: TWebLinkLabel; lblLogout: TWebLinkLabel;
lblVersion: TWebLabel; lblVersion: TWebLabel;
lblAppTitle: TWebLabel; lblAppTitle: TWebLabel;
memoDebug: TWebMemo;
xdwcMain: TXDataWebClient; xdwcMain: TXDataWebClient;
procedure WebFormCreate(Sender: TObject); procedure WebFormCreate(Sender: TObject);
procedure lblLogoutClick(Sender: TObject); procedure lblLogoutClick(Sender: TObject);
......
<div class="container-fluid p-2 d-flex flex-column h-100"> <div class="container-fluid p-2 d-flex flex-column h-100 overflow-hidden">
<div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0"> <div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0">
<h5 class="mb-0" id="lbl_project_name"></h5> <h5 class="mb-0" id="lbl_project_name"></h5>
<div class="d-flex gap-2">
<button id="btn_add_row" class="btn btn-sm btn-outline-success">Add Row</button> <div class="d-flex align-items-center gap-3">
<button id="btn_delete_row" class="btn btn-sm btn-outline-danger">Delete Row</button> <div id="lbl_total_rows"></div>
<button id="btn_reload" class="btn btn-sm btn-outline-primary">Reload</button>
<div class="d-flex gap-2">
<button id="btn_add_row" class="btn btn-sm btn-success">Add Row</button>
<button id="btn_delete_row" class="btn btn-sm btn-danger">Delete Row</button>
<button id="btn_reload" class="btn btn-sm btn-primary">Reload</button>
</div>
</div> </div>
</div> </div>
<div id="tasks_table_host" class="flex-grow-1 min-vh-0 overflow-auto"></div> <div id="tasks_table_host" class="flex-grow-1 min-h-0 overflow-auto"></div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNameManager" aria-labelledby="nm_title"> <div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNameManager" aria-labelledby="nm_title">
<div class="offcanvas-header"> <div class="offcanvas-header">
...@@ -34,6 +39,3 @@ ...@@ -34,6 +39,3 @@
</div> </div>
</div> </div>
</div> </div>
...@@ -43,6 +43,8 @@ type ...@@ -43,6 +43,8 @@ type
FPendingFocusField: string; FPendingFocusField: string;
FSelectedTaskItemId: Integer; FSelectedTaskItemId: Integer;
FSelectedTaskId: Integer; FSelectedTaskId: Integer;
FPendingScrollTop: Integer;
FPendingScrollLeft: Integer;
FPendingFocusItemNum: Integer; FPendingFocusItemNum: Integer;
FPendingFocusTaskField: string; FPendingFocusTaskField: string;
FNameManager: TNameManager; FNameManager: TNameManager;
...@@ -55,6 +57,7 @@ type ...@@ -55,6 +57,7 @@ type
procedure EnableAutoGrowTextAreas; procedure EnableAutoGrowTextAreas;
procedure GotoRowIndex(AIndex: Integer); procedure GotoRowIndex(AIndex: Integer);
function HtmlEncode(const s: string): string; function HtmlEncode(const s: string): string;
procedure SetTotalRowsLabel(ARowCount: Integer);
procedure SetTaskLabel(const ATitle: string); procedure SetTaskLabel(const ATitle: string);
[async] procedure SaveRow(AIndex: Integer); [async] procedure SaveRow(AIndex: Integer);
procedure EditorBlur(Event: TJSEvent); procedure EditorBlur(Event: TJSEvent);
...@@ -72,6 +75,8 @@ type ...@@ -72,6 +75,8 @@ type
procedure ApplySelectedRowState; procedure ApplySelectedRowState;
procedure ApplyPendingDeleteFocus; procedure ApplyPendingDeleteFocus;
procedure EditorKeyDown(Event: TJSEvent); procedure EditorKeyDown(Event: TJSEvent);
procedure CaptureTableScroll;
procedure RestoreTableScroll;
public public
end; end;
...@@ -322,7 +327,10 @@ begin ...@@ -322,7 +327,10 @@ begin
Utils.ShowSpinner('spinner'); Utils.ShowSpinner('spinner');
try try
if await(AddTaskRow) then if await(AddTaskRow) then
begin
CaptureTableScroll;
LoadTasks(FTaskId); LoadTasks(FTaskId);
end;
finally finally
Utils.HideSpinner('spinner'); Utils.HideSpinner('spinner');
end; end;
...@@ -362,6 +370,7 @@ begin ...@@ -362,6 +370,7 @@ begin
FPendingFocusItemNum := deletedItemNum; FPendingFocusItemNum := deletedItemNum;
FPendingFocusTaskField := 'application'; FPendingFocusTaskField := 'application';
CaptureTableScroll;
LoadTasks(FTaskId); LoadTasks(FTaskId);
end; end;
finally finally
...@@ -407,6 +416,9 @@ end; ...@@ -407,6 +416,9 @@ end;
[async] function TFTasksHTML.AddTaskRow: Boolean; [async] function TFTasksHTML.AddTaskRow: Boolean;
var var
response: TXDataClientResponse; response: TXDataClientResponse;
insertAfterItemNum: Integer;
newItemNum: Integer;
maxItemNum: Integer;
begin begin
Result := False; Result := False;
...@@ -416,11 +428,38 @@ begin ...@@ -416,11 +428,38 @@ begin
Exit; Exit;
end; end;
insertAfterItemNum := 0;
maxItemNum := 0;
if xdwdsTasks.Active then
begin
xdwdsTasks.First;
while not xdwdsTasks.Eof do
begin
if xdwdsTasksitemNum.AsInteger > maxItemNum then
maxItemNum := xdwdsTasksitemNum.AsInteger;
if xdwdsTaskstaskItemId.AsInteger = FSelectedTaskItemId then
insertAfterItemNum := xdwdsTasksitemNum.AsInteger;
xdwdsTasks.Next;
end;
end;
if insertAfterItemNum > 0 then
newItemNum := insertAfterItemNum + 1
else
newItemNum := maxItemNum + 1;
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.AddTaskRow', [FTaskId] 'IApiService.AddTaskRow', [FTaskId, insertAfterItemNum]
)); ));
console.log('AddTaskRow response=' + string(TJSJSON.stringify(response.Result))); console.log('AddTaskRow response=' + string(TJSJSON.stringify(response.Result)));
FPendingFocusItemNum := newItemNum;
FPendingFocusTaskField := 'application';
Result := True; Result := True;
except except
on E: EXDataClientRequestException do on E: EXDataClientRequestException do
...@@ -460,6 +499,16 @@ begin ...@@ -460,6 +499,16 @@ begin
end; end;
procedure TFTasksHTML.SetTotalRowsLabel(ARowCount: Integer);
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(document.getElementById('lbl_total_rows'));
if Assigned(el) then
el.innerText := 'Total Rows: ' + IntToStr(ARowCount);
end;
procedure TFTasksHTML.SetTaskLabel(const ATitle: string); procedure TFTasksHTML.SetTaskLabel(const ATitle: string);
var var
el: TJSHTMLElement; el: TJSHTMLElement;
...@@ -476,6 +525,7 @@ var ...@@ -476,6 +525,7 @@ var
resultObj, taskObj: TJSObject; resultObj, taskObj: TJSObject;
itemsArray: TJSArray; itemsArray: TJSArray;
titleText: string; titleText: string;
rowCount: Integer;
begin begin
console.log('IApiService.GetTaskItems called with task_id: ' + ATaskId); console.log('IApiService.GetTaskItems called with task_id: ' + ATaskId);
console.log('Load Tasks Fired'); console.log('Load Tasks Fired');
...@@ -506,6 +556,9 @@ begin ...@@ -506,6 +556,9 @@ begin
SetTaskLabel(titleText); SetTaskLabel(titleText);
rowCount := StrToIntDef(string(resultObj['count']), 0);
SetTotalRowsLabel(rowCount);
FReportedByOptions := ExtractOptionNames(TJSArray(resultObj['reportedByOptions'])); FReportedByOptions := ExtractOptionNames(TJSArray(resultObj['reportedByOptions']));
FAssignedToOptions := ExtractOptionNames(TJSArray(resultObj['assignedToOptions'])); FAssignedToOptions := ExtractOptionNames(TJSArray(resultObj['assignedToOptions']));
FStatusOptions := ExtractCodeDescs(TJSArray(resultObj['statusOptions'])); FStatusOptions := ExtractCodeDescs(TJSArray(resultObj['statusOptions']));
...@@ -682,19 +735,19 @@ begin ...@@ -682,19 +735,19 @@ begin
html := html :=
'<div class="tasks-vscroll">' + '<div class="tasks-vscroll">' +
'<div class="tasks-hscroll">' + '<div class="tasks-hscroll">' +
'<table class="table table-sm align-middle mb-0" style="min-width: 2000px;">' + '<table class="table table-sm table-bordered align-middle mb-0" style="min-width: 2000px;">' +
'<colgroup>' + '<colgroup>' +
'<col style="width:40px">' + '<col style="width:40px">' + // Item Num
'<col style="width:240px">' + '<col style="width:200px">' + // App
'<col style="width:90px">' + '<col style="width:90px">' + // Version
'<col style="width:120px">' + '<col style="width:120px">' + // Date
'<col style="width:120px">' + '<col style="width:120px">' + // Reported
'<col style="width:120px">' + '<col style="width:120px">' + // Assigned
'<col style="width:140px">' + '<col style="width:195px">' + // Status
'<col style="width:140px">' + '<col style="width:140px">' + // Status Date
'<col style="width:160px">' + '<col style="width:160px">' + // Form
'<col style="width:520px">' + '<col style="width:520px">' + // Issue
'<col style="width:520px">' + '<col style="width:520px">' + // Notes
'</colgroup>' + '</colgroup>' +
'<thead><tr>' + '<thead><tr>' +
ThBlank + ThBlank +
...@@ -739,6 +792,7 @@ begin ...@@ -739,6 +792,7 @@ begin
BindTableEditors; BindTableEditors;
EnableAutoGrowTextAreas; EnableAutoGrowTextAreas;
EnableColumnResize; EnableColumnResize;
RestoreTableScroll;
ApplyPendingFocus; ApplyPendingFocus;
ApplyPendingDeleteFocus; ApplyPendingDeleteFocus;
end; end;
...@@ -848,6 +902,7 @@ begin ...@@ -848,6 +902,7 @@ begin
FPendingFocusTaskItemId := movedTaskItemId; FPendingFocusTaskItemId := movedTaskItemId;
FPendingFocusField := 'application'; FPendingFocusField := 'application';
CaptureTableScroll;
LoadTasks(FTaskId); LoadTasks(FTaskId);
except except
on E: EXDataClientRequestException do on E: EXDataClientRequestException do
...@@ -1097,7 +1152,10 @@ end; ...@@ -1097,7 +1152,10 @@ end;
procedure TFTasksHTML.ApplyPendingDeleteFocus; procedure TFTasksHTML.ApplyPendingDeleteFocus;
var var
el: TJSHTMLElement; el: TJSHTMLElement;
rowEl: TJSHTMLElement;
selector: string; selector: string;
taskItemIdStr: string;
taskIdStr: string;
begin begin
if (FPendingFocusItemNum <= 0) or (FPendingFocusTaskField = '') then if (FPendingFocusItemNum <= 0) or (FPendingFocusTaskField = '') then
Exit; Exit;
...@@ -1109,6 +1167,19 @@ begin ...@@ -1109,6 +1167,19 @@ begin
el := TJSHTMLElement(document.querySelector(selector)); el := TJSHTMLElement(document.querySelector(selector));
if Assigned(el) then if Assigned(el) then
begin begin
rowEl := TJSHTMLElement(el.closest('tr'));
if Assigned(rowEl) then
begin
taskItemIdStr := string(rowEl.getAttribute('data-task-item-id'));
taskIdStr := string(rowEl.getAttribute('data-task-id'));
FSelectedTaskItemId := StrToIntDef(taskItemIdStr, 0);
FSelectedTaskId := StrToIntDef(taskIdStr, 0);
btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
ApplySelectedRowState;
end;
asm asm
el.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); el.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' });
el.focus(); el.focus();
...@@ -1119,6 +1190,28 @@ begin ...@@ -1119,6 +1190,28 @@ begin
FPendingFocusTaskField := ''; FPendingFocusTaskField := '';
end; end;
procedure TFTasksHTML.CaptureTableScroll;
begin
asm
const vscroll = document.querySelector('.tasks-vscroll');
const hscroll = document.querySelector('.tasks-hscroll');
this.FPendingScrollTop = vscroll ? vscroll.scrollTop : 0;
this.FPendingScrollLeft = hscroll ? hscroll.scrollLeft : 0;
end;
end;
procedure TFTasksHTML.RestoreTableScroll;
begin
asm
const vscroll = document.querySelector('.tasks-vscroll');
const hscroll = document.querySelector('.tasks-hscroll');
if (vscroll) vscroll.scrollTop = this.FPendingScrollTop || 0;
if (hscroll) hscroll.scrollLeft = this.FPendingScrollLeft || 0;
end;
end;
......
...@@ -53,7 +53,7 @@ input[data-field="itemNum"] { ...@@ -53,7 +53,7 @@ input[data-field="itemNum"] {
} }
.tasks-vscroll { .tasks-vscroll {
max-height: calc(100vh - 260px); height: 100%;
overflow: auto; overflow: auto;
} }
...@@ -68,5 +68,9 @@ input[data-field="itemNum"] { ...@@ -68,5 +68,9 @@ input[data-field="itemNum"] {
z-index: 3; z-index: 3;
} }
span.card {
border: none;
}
...@@ -99,11 +99,11 @@ ...@@ -99,11 +99,11 @@
<VerInfo_MajorVer>0</VerInfo_MajorVer> <VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer> <VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>8</VerInfo_Release> <VerInfo_Release>8</VerInfo_Release>
<TMSURLParams>?user_id=1019&amp;task_id=4000&amp;url_code=123456</TMSURLParams>
<TMSWebBrowser>1</TMSWebBrowser> <TMSWebBrowser>1</TMSWebBrowser>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
<TMSWebSingleInstance>1</TMSWebSingleInstance> <TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSURLParams>?user_id=1019&amp;task_id=4000&amp;url_code=123456</TMSURLParams>
<TMSUseJSDebugger>2</TMSUseJSDebugger> <TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''"> <PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols> <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
...@@ -170,7 +170,12 @@ ...@@ -170,7 +170,12 @@
<Source> <Source>
<Source Name="MainSource">emT3WebApp.dpr</Source> <Source Name="MainSource">emT3WebApp.dpr</Source>
</Source> </Source>
<Excluded_Packages/> <Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k370.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp370.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k370.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp370.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>
</Delphi.Personality> </Delphi.Personality>
<Deployment Version="5"> <Deployment Version="5">
<DeployFile LocalName="Win32\Debug\emT3WebApp.exe" Configuration="Debug" Class="ProjectOutput"> <DeployFile LocalName="Win32\Debug\emT3WebApp.exe" Configuration="Debug" Class="ProjectOutput">
......
...@@ -23,8 +23,8 @@ object ApiDatabase: TApiDatabase ...@@ -23,8 +23,8 @@ object ApiDatabase: TApiDatabase
SQL.Strings = ( SQL.Strings = (
'SELECT USER_ID, NAME, STATUS from users ORDER BY NAME') 'SELECT USER_ID, NAME, STATUS from users ORDER BY NAME')
OnCalcFields = uqUsersCalcFields OnCalcFields = uqUsersCalcFields
Left = 414 Left = 408
Top = 40 Top = 20
object uqUsersUSER_ID: TIntegerField object uqUsersUSER_ID: TIntegerField
FieldName = 'USER_ID' FieldName = 'USER_ID'
Required = True Required = True
...@@ -239,11 +239,7 @@ object ApiDatabase: TApiDatabase ...@@ -239,11 +239,7 @@ object ApiDatabase: TApiDatabase
')' ')'
'values (' 'values ('
' :TASK_ID,' ' :TASK_ID,'
' (' ' :ITEM_NUM,'
' select coalesce(max(ti.ITEM_NUM), 0) + 1'
' from task_items ti'
' where ti.TASK_ID = :TASK_ID'
' ),'
' '#39#39',' ' '#39#39','
' '#39#39',' ' '#39#39','
' curdate(),' ' curdate(),'
...@@ -263,6 +259,11 @@ object ApiDatabase: TApiDatabase ...@@ -263,6 +259,11 @@ object ApiDatabase: TApiDatabase
DataType = ftUnknown DataType = ftUnknown
Name = 'TASK_ID' Name = 'TASK_ID'
Value = nil Value = nil
end
item
DataType = ftUnknown
Name = 'ITEM_NUM'
Value = nil
end> end>
end end
object uqTaskHeader: TUniQuery object uqTaskHeader: TUniQuery
...@@ -604,4 +605,25 @@ object ApiDatabase: TApiDatabase ...@@ -604,4 +605,25 @@ object ApiDatabase: TApiDatabase
Value = nil Value = nil
end> end>
end end
object uqShiftTaskRowsForInsert: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items'
'set ITEM_NUM = ITEM_NUM + 1'
'where TASK_ID = :TASK_ID'
' and ITEM_NUM > :INSERT_AFTER_ITEM_NUM')
Left = 408
Top = 80
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'INSERT_AFTER_ITEM_NUM'
Value = nil
end>
end
end end
...@@ -66,6 +66,7 @@ type ...@@ -66,6 +66,7 @@ type
uqGetTaskRowPositionTASK_ITEM_ID: TIntegerField; uqGetTaskRowPositionTASK_ITEM_ID: TIntegerField;
uqDeleteTaskRow: TUniQuery; uqDeleteTaskRow: TUniQuery;
uqShiftTaskRowsAfterDelete: TUniQuery; uqShiftTaskRowsAfterDelete: TUniQuery;
uqShiftTaskRowsForInsert: TUniQuery;
procedure DataModuleCreate(Sender: TObject); procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet); procedure uqUsersCalcFields(DataSet: TDataSet);
private private
......
...@@ -92,7 +92,7 @@ type ...@@ -92,7 +92,7 @@ type
IApiService = interface(IInvokable) IApiService = interface(IInvokable)
['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}'] ['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}']
function GetTaskItems(taskId: string): TTaskItemsResponse; function GetTaskItems(taskId: string): TTaskItemsResponse;
[HttpPost] function AddTaskRow(taskId: string): Boolean; [HttpPost] function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
[HttpPost] function SaveTaskRow(Item: TTaskRowSave): Boolean; [HttpPost] function SaveTaskRow(Item: TTaskRowSave): Boolean;
function TestApi(messageText: string): TJSONObject; function TestApi(messageText: string): TJSONObject;
procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer); procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
......
...@@ -20,7 +20,7 @@ type ...@@ -20,7 +20,7 @@ type
private private
function BuildTaskNumber: string; function BuildTaskNumber: string;
function BuildTaskTitle(const taskNumber, projectName, subject: string): string; function BuildTaskTitle(const taskNumber, projectName, subject: string): string;
function AddTaskRow(taskId: string): Boolean; function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
function SaveTaskRow(Item: TTaskRowSave): Boolean; function SaveTaskRow(Item: TTaskRowSave): Boolean;
function TestApi(messageText: string): TJSONObject; function TestApi(messageText: string): TJSONObject;
function GetWebClientVersion: string; function GetWebClientVersion: string;
...@@ -177,20 +177,51 @@ begin ...@@ -177,20 +177,51 @@ begin
end; end;
function TApiService.AddTaskRow(taskId: string): Boolean; function TApiService.AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
var
newItemNum: Integer;
maxItemNum: Integer;
begin begin
Logger.Log(4, Format('ApiService.AddTaskRow - TASK_ID="%s"', [taskId])); Logger.Log(4, Format('ApiService.AddTaskRow - TASK_ID="%s" INSERT_AFTER_ITEM_NUM="%d"', [taskId, insertAfterItemNum]));
apiDB.uqGetTaskMaxItemNum.Close;
apiDB.uqGetTaskMaxItemNum.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqGetTaskMaxItemNum.Open;
maxItemNum := apiDB.uqGetTaskMaxItemNumMAX_ITEM_NUM.AsInteger;
if insertAfterItemNum < 0 then
insertAfterItemNum := 0;
if insertAfterItemNum > maxItemNum then
insertAfterItemNum := maxItemNum;
newItemNum := insertAfterItemNum + 1;
apiDB.uqAddTaskRow.Connection.StartTransaction;
try try
if insertAfterItemNum > 0 then
begin
apiDB.uqShiftTaskRowsForInsert.Close;
apiDB.uqShiftTaskRowsForInsert.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqShiftTaskRowsForInsert.ParamByName('INSERT_AFTER_ITEM_NUM').AsInteger := insertAfterItemNum;
apiDB.uqShiftTaskRowsForInsert.ExecSQL;
end;
apiDB.uqAddTaskRow.Close; apiDB.uqAddTaskRow.Close;
apiDB.uqAddTaskRow.ParamByName('TASK_ID').AsString := taskId; apiDB.uqAddTaskRow.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqAddTaskRow.ParamByName('ITEM_NUM').AsInteger := newItemNum;
apiDB.uqAddTaskRow.ExecSQL; apiDB.uqAddTaskRow.ExecSQL;
apiDB.uqAddTaskRow.Connection.Commit;
Result := True; Result := True;
Logger.Log(4, 'ApiService.AddTaskRow - OK'); Logger.Log(4, Format('ApiService.AddTaskRow - OK TASK_ID="%s" ITEM_NUM="%d"', [taskId, newItemNum]));
except except
on E: Exception do on E: Exception do
begin begin
if apiDB.uqAddTaskRow.Connection.InTransaction then
apiDB.uqAddTaskRow.Connection.Rollback;
Logger.Log(2, 'ApiService.AddTaskRow - ERROR: ' + E.Message); Logger.Log(2, 'ApiService.AddTaskRow - ERROR: ' + E.Message);
raise; raise;
end; end;
......
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