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>
......
...@@ -10,21 +10,21 @@ object ApiDatabase: TApiDatabase ...@@ -10,21 +10,21 @@ object ApiDatabase: TApiDatabase
Server = '192.168.102.131' Server = '192.168.102.131'
Connected = True Connected = True
LoginPrompt = False LoginPrompt = False
Left = 61 Left = 25
Top = 71 Top = 13
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF' EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end end
object MySQLUniProvider1: TMySQLUniProvider object MySQLUniProvider1: TMySQLUniProvider
Left = 170 Left = 56
Top = 72 Top = 14
end end
object uqUsers: TUniQuery object uqUsers: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
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 = 420 Left = 414
Top = 28 Top = 40
object uqUsersUSER_ID: TIntegerField object uqUsersUSER_ID: TIntegerField
FieldName = 'USER_ID' FieldName = 'USER_ID'
Required = True Required = True
...@@ -60,8 +60,8 @@ object ApiDatabase: TApiDatabase ...@@ -60,8 +60,8 @@ object ApiDatabase: TApiDatabase
' ISSUE = :ISSUE,' ' ISSUE = :ISSUE,'
' NOTES = :NOTES' ' NOTES = :NOTES'
'WHERE TASK_ITEM_ID = :TASK_ITEM_ID') 'WHERE TASK_ITEM_ID = :TASK_ITEM_ID')
Left = 424 Left = 408
Top = 138 Top = 242
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
...@@ -145,18 +145,16 @@ object ApiDatabase: TApiDatabase ...@@ -145,18 +145,16 @@ object ApiDatabase: TApiDatabase
'from task_items' 'from task_items'
'where TASK_ID = :TASK_ID' 'where TASK_ID = :TASK_ID'
'order by ITEM_NUM') 'order by ITEM_NUM')
Left = 422 Left = 62
Top = 84 Top = 138
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
Name = 'TASK_ID' Name = 'TASK_ID'
Value = nil Value = nil
end> end>
object uqTaskItemsTASK_ITEM_ID: TStringField object uqTaskItemsTASK_ITEM_ID: TIntegerField
FieldName = 'TASK_ITEM_ID' FieldName = 'TASK_ITEM_ID'
Required = True
Size = 7
end end
object uqTaskItemsTASK_ID: TStringField object uqTaskItemsTASK_ID: TStringField
FieldName = 'TASK_ID' FieldName = 'TASK_ID'
...@@ -221,14 +219,12 @@ object ApiDatabase: TApiDatabase ...@@ -221,14 +219,12 @@ object ApiDatabase: TApiDatabase
Size = 1000 Size = 1000
end end
end end
object uqEnsureBlankRow: TUniQuery object uqAddTaskRow: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
'insert ignore into task_items (' 'insert into task_items ('
' TASK_ITEM_ID,'
' TASK_ID,' ' TASK_ID,'
' ITEM_NUM,' ' ITEM_NUM,'
' PROJECT_ID,'
' APPLICATION,' ' APPLICATION,'
' APP_VERSION,' ' APP_VERSION,'
' TASK_DATE,' ' TASK_DATE,'
...@@ -243,10 +239,11 @@ object ApiDatabase: TApiDatabase ...@@ -243,10 +239,11 @@ object ApiDatabase: TApiDatabase
')' ')'
'values (' 'values ('
' :TASK_ID,' ' :TASK_ID,'
' :TASK_ID,' ' ('
' select coalesce(max(ti.ITEM_NUM), 0) + 1'
' coalesce((select PROJECT_ID from tasks where TASK_ID = :TASK_I' + ' from task_items ti'
'D), '#39#39'),' ' where ti.TASK_ID = :TASK_ID'
' ),'
' '#39#39',' ' '#39#39','
' '#39#39',' ' '#39#39','
' curdate(),' ' curdate(),'
...@@ -259,8 +256,8 @@ object ApiDatabase: TApiDatabase ...@@ -259,8 +256,8 @@ object ApiDatabase: TApiDatabase
' '#39#39',' ' '#39#39','
' '#39#39 ' '#39#39
')') ')')
Left = 426 Left = 412
Top = 192 Top = 300
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
...@@ -287,8 +284,8 @@ object ApiDatabase: TApiDatabase ...@@ -287,8 +284,8 @@ object ApiDatabase: TApiDatabase
'left join project p' 'left join project p'
' on p.PROJECT_ID = t.PROJECT_ID' ' on p.PROJECT_ID = t.PROJECT_ID'
'where t.TASK_ID = :TASK_ID') 'where t.TASK_ID = :TASK_ID')
Left = 172 Left = 60
Top = 240 Top = 242
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
...@@ -336,114 +333,6 @@ object ApiDatabase: TApiDatabase ...@@ -336,114 +333,6 @@ object ApiDatabase: TApiDatabase
Size = 30 Size = 30
end end
end end
object uqTaskItemsForParent: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select'
' ti.TASK_ITEM_ID,'
' ti.TASK_ID,'
' ti.ITEM_NUM,'
' ti.APPLICATION,'
' ti.APP_VERSION,'
' ti.TASK_DATE,'
' ti.STATUS_DATE,'
' ti.REPORTED_BY,'
' ti.ASSIGNED_TO,'
' ti.STATUS,'
' ti.FIXED_VERSION,'
' ti.FORM_SECTION,'
' ti.ISSUE,'
' ti.NOTES'
'from task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'where ti.TASK_ID = :TASK_ID'
' or t.PARENT_ID = :TASK_ID'
'order by'
' case when ti.TASK_ID = :TASK_ID then 0 else 1 end,'
' t.TASK_NUM_1,'
' t.TASK_NUM_2,'
' t.TASK_NUM_3,'
' t.TASK_NUM_4,'
' t.TASK_NUM_5,'
' t.TASK_NUM_6,'
' ti.ITEM_NUM')
Left = 172
Top = 188
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
object uqTaskItemsForParentTASK_ITEM_ID: TStringField
FieldName = 'TASK_ITEM_ID'
Required = True
Size = 7
end
object uqTaskItemsForParentTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqTaskItemsForParentITEM_NUM: TSmallintField
FieldName = 'ITEM_NUM'
Required = True
end
object uqTaskItemsForParentAPPLICATION: TStringField
FieldName = 'APPLICATION'
Required = True
Size = 255
end
object uqTaskItemsForParentAPP_VERSION: TStringField
FieldName = 'APP_VERSION'
Required = True
Size = 50
end
object uqTaskItemsForParentTASK_DATE: TDateField
FieldName = 'TASK_DATE'
Required = True
end
object uqTaskItemsForParentSTATUS_DATE: TDateField
FieldName = 'STATUS_DATE'
Required = True
end
object uqTaskItemsForParentREPORTED_BY: TStringField
FieldName = 'REPORTED_BY'
Required = True
Size = 50
end
object uqTaskItemsForParentASSIGNED_TO: TStringField
FieldName = 'ASSIGNED_TO'
Required = True
Size = 50
end
object uqTaskItemsForParentSTATUS: TStringField
FieldName = 'STATUS'
Required = True
Size = 100
end
object uqTaskItemsForParentFIXED_VERSION: TStringField
FieldName = 'FIXED_VERSION'
Required = True
Size = 50
end
object uqTaskItemsForParentFORM_SECTION: TStringField
FieldName = 'FORM_SECTION'
Required = True
Size = 255
end
object uqTaskItemsForParentISSUE: TStringField
FieldName = 'ISSUE'
Required = True
Size = 1000
end
object uqTaskItemsForParentNOTES: TStringField
FieldName = 'NOTES'
Required = True
Size = 1000
end
end
object uqTaskItemUsers: TUniQuery object uqTaskItemUsers: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
...@@ -455,8 +344,8 @@ object ApiDatabase: TApiDatabase ...@@ -455,8 +344,8 @@ object ApiDatabase: TApiDatabase
'from task_item_user' 'from task_item_user'
'where TASK_ID = :TASK_ID' 'where TASK_ID = :TASK_ID'
'order by USER_TYPE, NAME') 'order by USER_TYPE, NAME')
Left = 170 Left = 62
Top = 134 Top = 188
ParamData = < ParamData = <
item item
DataType = ftUnknown DataType = ftUnknown
...@@ -494,9 +383,8 @@ object ApiDatabase: TApiDatabase ...@@ -494,9 +383,8 @@ object ApiDatabase: TApiDatabase
'FROM etask.task_item_codes' 'FROM etask.task_item_codes'
'WHERE CODE_TYPE = '#39'STATUS'#39 'WHERE CODE_TYPE = '#39'STATUS'#39
'ORDER BY CODE') 'ORDER BY CODE')
Active = True Left = 60
Left = 428 Top = 296
Top = 248
object uqTaskItemCodesCODE: TStringField object uqTaskItemCodesCODE: TStringField
FieldName = 'CODE' FieldName = 'CODE'
Required = True Required = True
...@@ -511,4 +399,168 @@ object ApiDatabase: TApiDatabase ...@@ -511,4 +399,168 @@ object ApiDatabase: TApiDatabase
Size = 50 Size = 50
end end
end end
object uqGetTaskRowPosition: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select'
' TASK_ITEM_ID,'
' TASK_ID,'
' ITEM_NUM'
'from task_items'
'where TASK_ITEM_ID = :TASK_ITEM_ID'
' and TASK_ID = :TASK_ID')
Active = True
Left = 240
Top = 16
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ITEM_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
object uqGetTaskRowPositionTASK_ITEM_ID: TIntegerField
AutoGenerateValue = arAutoInc
FieldName = 'TASK_ITEM_ID'
end
object uqGetTaskRowPositionTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqGetTaskRowPositionITEM_NUM: TSmallintField
FieldName = 'ITEM_NUM'
Required = True
end
end
object uqMoveTaskRowTemp: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items'
'set ITEM_NUM = :TEMP_ITEM_NUM'
'where TASK_ITEM_ID = :TASK_ITEM_ID'
' and TASK_ID = :TASK_ID')
Left = 238
Top = 72
ParamData = <
item
DataType = ftUnknown
Name = 'TEMP_ITEM_NUM'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ITEM_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
end
object uqShiftTaskRowsDown: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items'
'set ITEM_NUM = ITEM_NUM + 1'
'where TASK_ID = :TASK_ID'
' and ITEM_NUM >= :NEW_ITEM_NUM'
' and ITEM_NUM < :OLD_ITEM_NUM')
Left = 234
Top = 132
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'NEW_ITEM_NUM'
Value = nil
end
item
DataType = ftUnknown
Name = 'OLD_ITEM_NUM'
Value = nil
end>
end
object uqShiftTaskRowsUp: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items'
'set ITEM_NUM = ITEM_NUM - 1'
'where TASK_ID = :TASK_ID'
' and ITEM_NUM > :OLD_ITEM_NUM'
' and ITEM_NUM <= :NEW_ITEM_NUM')
Left = 236
Top = 184
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'OLD_ITEM_NUM'
Value = nil
end
item
DataType = ftUnknown
Name = 'NEW_ITEM_NUM'
Value = nil
end>
end
object uqSetTaskRowFinalPosition: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items'
'set ITEM_NUM = :NEW_ITEM_NUM'
'where TASK_ITEM_ID = :TASK_ITEM_ID'
' and TASK_ID = :TASK_ID')
Left = 234
Top = 232
ParamData = <
item
DataType = ftUnknown
Name = 'NEW_ITEM_NUM'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ITEM_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
end
object uqGetTaskMaxItemNum: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select'
' coalesce(max(ITEM_NUM), 0) as MAX_ITEM_NUM'
'from task_items'
'where TASK_ID = :TASK_ID')
Left = 236
Top = 290
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
object uqGetTaskMaxItemNumMAX_ITEM_NUM: TIntegerField
FieldName = 'MAX_ITEM_NUM'
ReadOnly = True
end
end
end end
...@@ -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