Commit 41b5088d by Mac Stephens

Add server-backed task row insertion with next item_num, and remove old…

Add server-backed task row insertion with next item_num, and remove old blank-row auto-create behavior. Implement row reordering by editable item_num, including transactional shifting logic and integer task_item_id cleanup across server/client.
parent 07dfa883
...@@ -22,3 +22,9 @@ emT3Web/__recovery/ ...@@ -22,3 +22,9 @@ emT3Web/__recovery/
emT3WebApp/__history/ emT3WebApp/__history/
*.zip *.zip
emT3WebApp/css/__history/
emT3XDataServer/bin/static/
emT3WebApp/Win32/Debug/
...@@ -71,7 +71,7 @@ object FTasksHTML: TFTasksHTML ...@@ -71,7 +71,7 @@ object FTasksHTML: TFTasksHTML
object xdwdsTasksnotes: TStringField object xdwdsTasksnotes: TStringField
FieldName = 'notes' FieldName = 'notes'
end end
object xdwdsTaskstaskItemId: TStringField object xdwdsTaskstaskItemId: TIntegerField
FieldName = 'taskItemId' FieldName = 'taskItemId'
end end
end end
......
...@@ -27,8 +27,8 @@ type ...@@ -27,8 +27,8 @@ type
xdwdsTasksnotes: TStringField; xdwdsTasksnotes: TStringField;
btnReload: TWebButton; btnReload: TWebButton;
btnAddRow: TWebButton; btnAddRow: TWebButton;
xdwdsTaskstaskItemId: TStringField;
xdwdsTasksitemNum: TIntegerField; xdwdsTasksitemNum: TIntegerField;
xdwdsTaskstaskItemId: TIntegerField;
[async] procedure btnAddRowClick(Sender: TObject); [async] procedure btnAddRowClick(Sender: TObject);
procedure btnReloadClick(Sender: TObject); procedure btnReloadClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject); procedure WebFormCreate(Sender: TObject);
...@@ -57,6 +57,7 @@ type ...@@ -57,6 +57,7 @@ type
procedure DropdownItemClick(Event: TJSEvent); procedure DropdownItemClick(Event: TJSEvent);
procedure DropdownEditClick(Event: TJSEvent); procedure DropdownEditClick(Event: TJSEvent);
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray; function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
[async] procedure MoveTaskRow(AIndex: Integer; const newItemNum: Integer);
public public
end; end;
...@@ -173,6 +174,9 @@ begin ...@@ -173,6 +174,9 @@ begin
console.log('EditorInput: idx=' + IntToStr(idx) + ' field=' + fieldName + ' val=' + newVal); console.log('EditorInput: idx=' + IntToStr(idx) + ' field=' + fieldName + ' val=' + newVal);
xdwdsTasks.Edit; xdwdsTasks.Edit;
if SameText(fieldName, 'itemNum') then
xdwdsTasks.FieldByName(fieldName).AsInteger := StrToIntDef(newVal, 0)
else
xdwdsTasks.FieldByName(fieldName).AsString := newVal; xdwdsTasks.FieldByName(fieldName).AsString := newVal;
xdwdsTasks.Post; xdwdsTasks.Post;
...@@ -382,6 +386,8 @@ begin ...@@ -382,6 +386,8 @@ begin
xdwdsTasks.SetJsonData(itemsArray); xdwdsTasks.SetJsonData(itemsArray);
xdwdsTasks.Open; xdwdsTasks.Open;
btnAddRow.Enabled := True;
RenderTable; RenderTable;
finally finally
Utils.HideSpinner('spinner'); Utils.HideSpinner('spinner');
...@@ -433,16 +439,18 @@ var ...@@ -433,16 +439,18 @@ var
begin begin
Result := Result :=
'<textarea class="form-control form-control-sm cell-textarea task-editor w-100" ' + '<textarea class="form-control form-control-sm cell-textarea task-editor w-100" ' +
'style="height:31px; min-height:31px; overflow:hidden; resize:none;" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' + 'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'rows="2">' + HtmlEncode(Value) + '</textarea>'; 'rows="1">' + HtmlEncode(Value) + '</textarea>';
end; end;
function RowNumCell(const Value: Integer): string; function ItemNumInput(const Value: Integer; const AIdx: Integer): string;
begin begin
Result := Result :=
'<div class="form-control form-control-sm text-center bg-light-subtle">' + '<input type="number" min="1" class="form-control form-control-sm task-editor text-center px-1" ' +
IntToStr(Value) + 'style="width: 30px;" ' +
'</div>'; 'data-idx="' + IntToStr(AIdx) + '" data-field="itemNum" ' +
'value="' + IntToStr(Value) + '">';
end; end;
function DateInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string; function DateInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string;
...@@ -541,7 +549,7 @@ begin ...@@ -541,7 +549,7 @@ begin
html := html :=
'<div class="tasks-vscroll">' + '<div class="tasks-vscroll">' +
'<div class="tasks-hscroll">' + '<div class="tasks-hscroll">' +
'<table class="table table-sm table-bordered table-hover align-middle mb-0">' + '<table class="table table-sm table-bordered align-middle mb-0">' +
'<colgroup>' + '<colgroup>' +
'<col style="width:40px">' + '<col style="width:40px">' +
'<col style="width:240px">' + '<col style="width:240px">' +
...@@ -575,7 +583,7 @@ begin ...@@ -575,7 +583,7 @@ begin
begin begin
html := html + html := html +
'<tr>' + '<tr>' +
TdNowrap(RowNumCell(xdwdsTasksitemNum.AsInteger)) + TdNowrap(ItemNumInput(xdwdsTasksitemNum.AsInteger, rowIdx)) +
TdNowrap(TextInput('application', xdwdsTasksapplication.AsString, rowIdx, 180)) + TdNowrap(TextInput('application', xdwdsTasksapplication.AsString, rowIdx, 180)) +
TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx, 80)) + TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx, 80)) +
TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx, 110)) + TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx, 110)) +
...@@ -645,6 +653,7 @@ var ...@@ -645,6 +653,7 @@ var
el: TJSHTMLElement; el: TJSHTMLElement;
idx: Integer; idx: Integer;
idxStr, fieldName: string; idxStr, fieldName: string;
newItemNum: Integer;
begin begin
el := TJSHTMLElement(Event.target); el := TJSHTMLElement(Event.target);
...@@ -663,12 +672,52 @@ begin ...@@ -663,12 +672,52 @@ begin
if idx < 0 then if idx < 0 then
Exit; Exit;
console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName); if SameText(fieldName, 'itemNum') then
begin
newItemNum := StrToIntDef(string(TJSObject(el)['value']), 0);
console.log('EditorBlur: MOVE idx=' + IntToStr(idx) + ' newItemNum=' + IntToStr(newItemNum));
MoveTaskRow(idx, newItemNum);
Exit;
end;
console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName);
SaveRow(idx); SaveRow(idx);
end; end;
[async] procedure TFTasksHTML.MoveTaskRow(AIndex: Integer; const newItemNum: Integer);
var
response: TXDataClientResponse;
begin
if not xdwdsTasks.Active then
Exit;
GotoRowIndex(AIndex);
if xdwdsTasks.Eof then
Exit;
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.MoveTaskRow',
[
StrToIntDef(xdwdsTaskstaskId.AsString, 0),
xdwdsTaskstaskItemId.AsInteger,
newItemNum
]
));
console.log('MoveTaskRow: response=' + string(TJSJSON.stringify(response.Result)));
LoadTasks(FTaskId);
except
on E: EXDataClientRequestException do
begin
console.log('MoveTaskRow ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
LoadTasks(FTaskId);
end;
end;
end;
[async] procedure TFTasksHTML.SaveRow(AIndex: Integer); [async] procedure TFTasksHTML.SaveRow(AIndex: Integer);
const const
// Note: Use this to manipulate saving to the server or not for testing // Note: Use this to manipulate saving to the server or not for testing
...@@ -687,7 +736,7 @@ begin ...@@ -687,7 +736,7 @@ begin
payload := TJSObject.new; payload := TJSObject.new;
payload['taskItemId'] := xdwdsTaskstaskItemId.AsString; payload['taskItemId'] := xdwdsTaskstaskItemId.AsInteger;
payload['taskId'] := xdwdsTaskstaskId.AsString; payload['taskId'] := xdwdsTaskstaskId.AsString;
payload['application'] := xdwdsTasksapplication.AsString; payload['application'] := xdwdsTasksapplication.AsString;
......
[Paths]
HtmlPath=C:\Projects\emT3web\emT3XDataServer\bin\static
HtmlFile=index.html
DefaultURL=http://localhost:8000/emT3WebApp
SingleInstance=0
Debug=0
DebugManager=C:\RADTools\TMS\Products\tms.webcore\Bin\Win32\TMSDBGManager.exe
URL=http://localhost:8000/$(ProjectName)
URLParams=?user_id=1019&task_id=4000&url_code=123456
Browser=1
BrowserBin=
BrowserParams=
Electron=0
ElectronBuild=0
JSDebugger=1
...@@ -39,5 +39,16 @@ is-invalid .form-check-input { ...@@ -39,5 +39,16 @@ is-invalid .form-check-input {
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
/* This hides the up and down arrows on the item_num box, comment or remove it to add them back */
input[data-field="itemNum"]::-webkit-outer-spin-button,
input[data-field="itemNum"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[data-field="itemNum"] {
-moz-appearance: textfield;
appearance: textfield;
}
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<ProjectGuid>{DB6F5DBF-7E4B-45DA-AFFA-6C8DF15BA740}</ProjectGuid> <ProjectGuid>{DB6F5DBF-7E4B-45DA-AFFA-6C8DF15BA740}</ProjectGuid>
<ProjectVersion>20.3</ProjectVersion> <ProjectVersion>20.4</ProjectVersion>
<FrameworkType>VCL</FrameworkType> <FrameworkType>VCL</FrameworkType>
<MainSource>emT3WebApp.dpr</MainSource> <MainSource>emT3WebApp.dpr</MainSource>
<Base>True</Base> <Base>True</Base>
...@@ -907,6 +907,9 @@ ...@@ -907,6 +907,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">
...@@ -977,6 +980,10 @@ ...@@ -977,6 +980,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_DelphiLogo44"> <DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32"> <Platform Name="Win32">
...@@ -987,6 +994,10 @@ ...@@ -987,6 +994,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">
...@@ -1201,6 +1212,7 @@ ...@@ -1201,6 +1212,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>
......
...@@ -18,8 +18,7 @@ type ...@@ -18,8 +18,7 @@ type
uqUsersREPRESENTATIVE: TStringField; uqUsersREPRESENTATIVE: TStringField;
uqSaveTaskRow: TUniQuery; uqSaveTaskRow: TUniQuery;
uqTaskItems: TUniQuery; uqTaskItems: TUniQuery;
uqEnsureBlankRow: TUniQuery; uqAddTaskRow: TUniQuery;
uqTaskItemsTASK_ITEM_ID: TStringField;
uqTaskItemsTASK_ID: TStringField; uqTaskItemsTASK_ID: TStringField;
uqTaskItemsITEM_NUM: TShortintField; uqTaskItemsITEM_NUM: TShortintField;
uqTaskItemsAPPLICATION: TStringField; uqTaskItemsAPPLICATION: TStringField;
...@@ -45,21 +44,6 @@ type ...@@ -45,21 +44,6 @@ type
uqTaskHeaderPROJECT_ID: TStringField; uqTaskHeaderPROJECT_ID: TStringField;
uqTaskHeaderSUBJECT: TStringField; uqTaskHeaderSUBJECT: TStringField;
uqTaskHeaderPROJECT_NAME: TStringField; uqTaskHeaderPROJECT_NAME: TStringField;
uqTaskItemsForParent: TUniQuery;
uqTaskItemsForParentTASK_ITEM_ID: TStringField;
uqTaskItemsForParentTASK_ID: TStringField;
uqTaskItemsForParentITEM_NUM: TSmallintField;
uqTaskItemsForParentAPPLICATION: TStringField;
uqTaskItemsForParentAPP_VERSION: TStringField;
uqTaskItemsForParentTASK_DATE: TDateField;
uqTaskItemsForParentSTATUS_DATE: TDateField;
uqTaskItemsForParentREPORTED_BY: TStringField;
uqTaskItemsForParentASSIGNED_TO: TStringField;
uqTaskItemsForParentSTATUS: TStringField;
uqTaskItemsForParentFIXED_VERSION: TStringField;
uqTaskItemsForParentFORM_SECTION: TStringField;
uqTaskItemsForParentISSUE: TStringField;
uqTaskItemsForParentNOTES: TStringField;
uqTaskItemUsers: TUniQuery; uqTaskItemUsers: TUniQuery;
uqTaskItemUsersTASK_ITEM_USER_ID: TStringField; uqTaskItemUsersTASK_ITEM_USER_ID: TStringField;
uqTaskItemUsersTASK_ID: TStringField; uqTaskItemUsersTASK_ID: TStringField;
...@@ -69,6 +53,17 @@ type ...@@ -69,6 +53,17 @@ type
uqTaskItemCodesCODE: TStringField; uqTaskItemCodesCODE: TStringField;
uqTaskItemCodesCODE_DESC: TStringField; uqTaskItemCodesCODE_DESC: TStringField;
uqTaskItemCodesCODE_TYPE: TStringField; uqTaskItemCodesCODE_TYPE: TStringField;
uqGetTaskRowPosition: TUniQuery;
uqMoveTaskRowTemp: TUniQuery;
uqShiftTaskRowsDown: TUniQuery;
uqShiftTaskRowsUp: TUniQuery;
uqSetTaskRowFinalPosition: TUniQuery;
uqGetTaskMaxItemNum: TUniQuery;
uqGetTaskRowPositionTASK_ID: TStringField;
uqGetTaskRowPositionITEM_NUM: TSmallintField;
uqGetTaskMaxItemNumMAX_ITEM_NUM: TIntegerField;
uqTaskItemsTASK_ITEM_ID: TIntegerField;
uqGetTaskRowPositionTASK_ITEM_ID: TIntegerField;
procedure DataModuleCreate(Sender: TObject); procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet); procedure uqUsersCalcFields(DataSet: TDataSet);
private private
......
...@@ -17,7 +17,7 @@ const ...@@ -17,7 +17,7 @@ const
type type
TTaskItem = class TTaskItem = class
public public
taskItemId: string; taskItemId: integer;
taskId: string; taskId: string;
itemNum: integer; itemNum: integer;
application: string; application: string;
...@@ -70,7 +70,7 @@ type ...@@ -70,7 +70,7 @@ type
TTaskRowSave = class TTaskRowSave = class
public public
taskItemId: string; taskItemId: integer;
taskId: string; taskId: string;
taskNum: integer; taskNum: integer;
application: string; application: string;
...@@ -92,9 +92,10 @@ type ...@@ -92,9 +92,10 @@ 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): 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);
end; end;
implementation implementation
......
...@@ -18,13 +18,13 @@ type ...@@ -18,13 +18,13 @@ type
strict private strict private
apiDB: TApiDatabase; apiDB: TApiDatabase;
private private
procedure EnsureBlankWebTaskRow(const taskId: string);
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): 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;
procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
public public
procedure AfterConstruction; override; procedure AfterConstruction; override;
procedure BeforeDestruction; override; procedure BeforeDestruction; override;
...@@ -128,22 +128,12 @@ begin ...@@ -128,22 +128,12 @@ begin
apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId; apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItems.Open; apiDB.uqTaskItems.Open;
if apiDB.uqTaskItems.IsEmpty then
begin
Logger.Log(4, Format('ApiService.GetTaskItems - no rows for TASK_ID="%s", ensuring blank row', [taskId]));
EnsureBlankWebTaskRow(taskId);
apiDB.uqTaskItems.Close;
apiDB.uqTaskItems.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskItems.Open;
end;
while not apiDB.uqTaskItems.Eof do while not apiDB.uqTaskItems.Eof do
begin begin
item := TTaskItem.Create; item := TTaskItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(item); TXDataOperationContext.Current.Handler.ManagedObjects.Add(item);
item.taskItemId := apiDB.uqTaskItemsTASK_ITEM_ID.AsString; item.taskItemId := apiDB.uqTaskItemsTASK_ITEM_ID.AsInteger;
item.taskId := apiDB.uqTaskItemsTASK_ID.AsString; item.taskId := apiDB.uqTaskItemsTASK_ID.AsString;
item.itemNum := apiDB.uqTaskItemsITEM_NUM.AsInteger; item.itemNum := apiDB.uqTaskItemsITEM_NUM.AsInteger;
...@@ -186,17 +176,118 @@ begin ...@@ -186,17 +176,118 @@ begin
end; end;
procedure TApiService.EnsureBlankWebTaskRow(const taskId: string); function TApiService.AddTaskRow(taskId: string): Boolean;
begin begin
Logger.Log(4, Format('ApiService.EnsureBlankWebTaskRow - TASK_ID="%s"', [taskId])); Logger.Log(4, Format('ApiService.AddTaskRow - TASK_ID="%s"', [taskId]));
try try
apiDB.uqEnsureBlankRow.Close; apiDB.uqAddTaskRow.Close;
apiDB.uqEnsureBlankRow.ParamByName('TASK_ID').AsString := taskId; apiDB.uqAddTaskRow.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqEnsureBlankRow.ExecSQL; apiDB.uqAddTaskRow.ExecSQL;
Result := True;
Logger.Log(4, 'ApiService.AddTaskRow - OK');
except except
on E: Exception do on E: Exception do
begin begin
Logger.Log(2, 'ApiService.EnsureBlankWebTaskRow - ERROR: ' + E.Message); Logger.Log(2, 'ApiService.AddTaskRow - ERROR: ' + E.Message);
raise;
end;
end;
end;
procedure TApiService.MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
var
oldItemNum: Integer;
maxItemNum: Integer;
finalItemNum: Integer;
tempItemNum: Integer;
begin
logger.log(4, 'MoveTaskRow start TASK_ID=' + IntToStr(taskId) +
' TASK_ITEM_ID=' + IntToStr(taskItemId) +
' NEW_ITEM_NUM=' + IntToStr(newItemNum));
apiDB.uqGetTaskRowPosition.Close;
apiDB.uqGetTaskRowPosition.ParamByName('TASK_ID').AsInteger := taskId;
apiDB.uqGetTaskRowPosition.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
apiDB.uqGetTaskRowPosition.Open;
if apiDB.uqGetTaskRowPosition.IsEmpty then
begin
logger.log(2, 'MoveTaskRow row not found TASK_ID=' + IntToStr(taskId) +
' TASK_ITEM_ID=' + IntToStr(taskItemId));
raise Exception.Create('Task row not found.');
end;
oldItemNum := apiDB.uqGetTaskRowPositionITEM_NUM.AsInteger;
apiDB.uqGetTaskMaxItemNum.Close;
apiDB.uqGetTaskMaxItemNum.ParamByName('TASK_ID').AsInteger := taskId;
apiDB.uqGetTaskMaxItemNum.Open;
maxItemNum := apiDB.uqGetTaskMaxItemNumMAX_ITEM_NUM.AsInteger;
finalItemNum := newItemNum;
if finalItemNum < 1 then
finalItemNum := 1;
if finalItemNum > maxItemNum then
finalItemNum := maxItemNum;
if finalItemNum = oldItemNum then
begin
logger.log(4, 'MoveTaskRow no move needed TASK_ID=' + IntToStr(taskId) +
' TASK_ITEM_ID=' + IntToStr(taskItemId) +
' ITEM_NUM=' + IntToStr(oldItemNum));
Exit;
end;
tempItemNum := -1;
apiDB.uqGetTaskRowPosition.Connection.StartTransaction;
try
apiDB.uqMoveTaskRowTemp.ParamByName('TEMP_ITEM_NUM').AsInteger := tempItemNum;
apiDB.uqMoveTaskRowTemp.ParamByName('TASK_ID').AsInteger := taskId;
apiDB.uqMoveTaskRowTemp.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
apiDB.uqMoveTaskRowTemp.ExecSQL;
if finalItemNum < oldItemNum then
begin
apiDB.uqShiftTaskRowsDown.ParamByName('TASK_ID').AsInteger := taskId;
apiDB.uqShiftTaskRowsDown.ParamByName('NEW_ITEM_NUM').AsInteger := finalItemNum;
apiDB.uqShiftTaskRowsDown.ParamByName('OLD_ITEM_NUM').AsInteger := oldItemNum;
apiDB.uqShiftTaskRowsDown.ExecSQL;
end
else
begin
apiDB.uqShiftTaskRowsUp.ParamByName('TASK_ID').AsInteger := taskId;
apiDB.uqShiftTaskRowsUp.ParamByName('OLD_ITEM_NUM').AsInteger := oldItemNum;
apiDB.uqShiftTaskRowsUp.ParamByName('NEW_ITEM_NUM').AsInteger := finalItemNum;
apiDB.uqShiftTaskRowsUp.ExecSQL;
end;
apiDB.uqSetTaskRowFinalPosition.ParamByName('TASK_ID').AsInteger := taskId;
apiDB.uqSetTaskRowFinalPosition.ParamByName('TASK_ITEM_ID').AsInteger := taskItemId;
apiDB.uqSetTaskRowFinalPosition.ParamByName('NEW_ITEM_NUM').AsInteger := finalItemNum;
apiDB.uqSetTaskRowFinalPosition.ExecSQL;
apiDB.uqGetTaskRowPosition.Connection.Commit;
logger.log(4, 'MoveTaskRow success TASK_ID=' + IntToStr(taskId) +
' TASK_ITEM_ID=' + IntToStr(taskItemId) +
' OLD_ITEM_NUM=' + IntToStr(oldItemNum) +
' NEW_ITEM_NUM=' + IntToStr(finalItemNum));
except
on E: Exception do
begin
if apiDB.uqGetTaskRowPosition.Connection.InTransaction then
apiDB.uqGetTaskRowPosition.Connection.Rollback;
logger.log(2, 'MoveTaskRow failed TASK_ID=' + IntToStr(taskId) +
' TASK_ITEM_ID=' + IntToStr(taskItemId) +
' ERROR=' + E.Message);
raise; raise;
end; end;
end; end;
...@@ -258,12 +349,12 @@ function TApiService.SaveTaskRow(Item: TTaskRowSave): Boolean; ...@@ -258,12 +349,12 @@ function TApiService.SaveTaskRow(Item: TTaskRowSave): Boolean;
var var
d: TDateTime; d: TDateTime;
begin begin
Logger.Log(4, Format('ApiService.SaveTaskRow - TASK_ITEM_ID="%s"', [Item.taskItemId])); Logger.Log(4, Format('ApiService.SaveTaskRow - TASK_ITEM_ID="%d"', [Item.taskItemId]));
try try
apiDB.uqSaveTaskRow.Close; apiDB.uqSaveTaskRow.Close;
apiDB.uqSaveTaskRow.ParamByName('TASK_ITEM_ID').AsString := Item.taskItemId; apiDB.uqSaveTaskRow.ParamByName('TASK_ITEM_ID').AsInteger := Item.taskItemId;
apiDB.uqSaveTaskRow.ParamByName('APPLICATION').AsString := Item.application; apiDB.uqSaveTaskRow.ParamByName('APPLICATION').AsString := Item.application;
apiDB.uqSaveTaskRow.ParamByName('APP_VERSION').AsString := Item.version; apiDB.uqSaveTaskRow.ParamByName('APP_VERSION').AsString := Item.version;
......
<div id="div_wrapper">
<nav class="navbar navbar-expand-lg bg-body-tertiary border-bottom shadow-sm">
<div class="container-fluid">
<div class="d-flex align-items-center gap-2">
<a id="lbl_app_title" class="navbar-brand fw-semibold" href="index.html">Koehler-Gibson Orders</a>
<span id="lbl_version" class="badge text-bg-light border text-muted fw-normal"></span>
</div>
<div class="collapse navbar-collapse show" id="pnl_navbar_nav_dropdown">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2" id="lnk_navbar_dropdown_menu_link"
role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-user fa-fw"></i>
<span id="lbl_username" class="fw-semibold">Username</span>
</a>
<ul class="dropdown-menu dropdown-menu-end shadow-sm" aria-labelledby="lnk_navbar_dropdown_menu_link">
<li>
<a class="dropdown-item d-flex align-items-center gap-2" id="lbl_home" href="#">
<i class="fa fa-home fa-fw"></i><span>Home</span>
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2" id="lbl_user_profile" href="#">
<i class="fa fa-user fa-fw"></i><span>User Profile</span>
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item d-flex align-items-center gap-2 text-danger" id="lbl_logout" href="#">
<i class="fa fa-sign-out fa-fw"></i><span>Logout</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Toast -->
<div id="pnl_toast_wrapper" class="position-fixed top-0 start-0 mt-5 ms-4"
style="z-index: 1080; min-width: 300px; max-width: 500px;">
<div id="toast_bootstrap" class="toast align-items-center text-white bg-success border-0 shadow" role="alert"
aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body" id="lbl_bootstrap_toast_body">
Success message
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
</div>
</div>
<!-- Main Panel (where all forms display) -->
<div class="container-fluid py-3">
<div class="row">
<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>
<!-- Spinner Modal -->
<div id="div_spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
<div></div><div></div><div></div><div></div>
<div></div><div></div><div></div><div></div>
</div>
</div>
<!-- Error Modal -->
<div class="modal fade" id="mdl_error" tabindex="-1" aria-labelledby="lbl_modal_title" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="lbl_modal_title">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="lbl_modal_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary">Back to Orders</button>
</div>
</div>
</div>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="mdl_confirmation" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fw-bold" id="lbl_confirmation_body">
Placeholder text
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary me-3" id="btn_confirm_left">Cancel</button>
<button type="button" class="btn btn-secondary" id="btn_confirm_right">Confirm</button>
</div>
</div>
</div>
</div>
<!-- Notification Modal -->
<div class="modal fade" id="mdl_notification" tabindex="-1" aria-labelledby="lbl_notification_title"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="lbl_notification_title">Info</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="lbl_notification_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_close" class="btn btn-primary">Close</button>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid p-2 d-flex flex-column h-100">
<div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0">
<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>
<button id="btn_reload" class="btn btn-sm btn-outline-primary">Reload</button>
</div>
</div>
<div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNameManager" aria-labelledby="nm_title">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="nm_title">Add Item</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div id="nm_existing_list" class="list-group mb-3"></div>
<div id="nm_add_wrap" class="d-none mb-3">
<input id="nm_name_input" type="text" class="form-control" maxlength="100">
<div id="nm_name_invalid" class="invalid-feedback d-none"></div>
<div class="d-flex justify-content-end mt-2">
<button id="btn_nm_save" type="button" class="btn btn-success">Save</button>
</div>
</div>
<button id="btn_nm_add_another" type="button" class="btn btn-secondary">
Add another item
</button>
</div>
</div>
</div>
<div class="container py-4">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
<div>
<div id="lbl_test" class="h2 fw-semibold mb-1">Test Form</div>
<div class="text-muted">Quick API + JWT/version diagnostics</div>
</div>
<button id="btn_test_api" class="btn btn-primary">Test API</button>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-body-tertiary d-flex align-items-center justify-content-between">
<div class="fw-semibold">Debug Output</div>
</div>
<div class="card-body" style="min-height: 70vh;">
<textarea id="memo_test_debug"
class="form-control font-monospace h-100"
style="min-height: 65vh;"
rows="28"
placeholder="Click Test API to populate diagnostics..."></textarea>
</div>
</div>
</div>
{
"AuthUrl" : "http://localhost:2001/emsys/emt3/auth/",
"ApiUrl" : "http://localhost:2001/emsys/emt3/api/"
}
is-invalid .form-check-input {
border: 1px solid #dc3545 !important;
}
.is-invalid .form-check-label {
color: #dc3545 !important;
}
.btn-primary {
background-color: #286090 !important;
border-color: #286090 !important;
color: #fff !important;
}
.btn-primary:hover {
background-color: #204d74 !important;
border-color: #204d74 !important;
}
@keyframes slideInLeft {
from {
transform: translateX(-120%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.toast.slide-in {
animation: slideInLeft 0.4s ease-out forwards;
}
#spinner {
position: fixed !important;
z-index: 9999 !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background: #204d74;
margin: -5px 0 0 -5px;
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<link href="data:;base64,=" rel="icon"/>
<title>EM Systems Template App</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="css/app.css" rel="stylesheet"/>
<link href="css/spinner.css" rel="stylesheet"/>
<script crossorigin="anonymous" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" src="https://code.jquery.com/jquery-3.7.1.js"></script>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script src="emT3WebApp.js"></script>
</head>
<body>
<noscript>Your browser does not support JavaScript!</noscript>
<script>rtl.run();</script>
</body>
</html>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<ProjectGuid>{2A3028D9-BC39-4625-9BA5-0338012E2824}</ProjectGuid> <ProjectGuid>{2A3028D9-BC39-4625-9BA5-0338012E2824}</ProjectGuid>
<ProjectVersion>20.3</ProjectVersion> <ProjectVersion>20.4</ProjectVersion>
<FrameworkType>VCL</FrameworkType> <FrameworkType>VCL</FrameworkType>
<Base>True</Base> <Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config> <Config Condition="'$(Config)'==''">Debug</Config>
...@@ -872,6 +872,9 @@ ...@@ -872,6 +872,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">
...@@ -942,6 +945,10 @@ ...@@ -942,6 +945,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_DelphiLogo44"> <DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32"> <Platform Name="Win32">
...@@ -952,6 +959,10 @@ ...@@ -952,6 +959,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">
...@@ -1165,6 +1176,7 @@ ...@@ -1165,6 +1176,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>
......
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