Commit 9ca47a7c by Mac Stephens

Merge branch 'master' into dev

parents 8a2f5e16 72262daa
emT3VCLDemo/__history/
emT3VCLDemo/__recovery/
emT3VCLDemo/Win64x/
emT3Web/__history/
emT3Web/__recovery/
......@@ -19,4 +20,5 @@ emT3XDataServer/Win32/
*.txt
*.zip
emT3VCLDemo/__recovery/
emT3Web/css/__history/
......@@ -20,7 +20,7 @@ type
FUnauthorizedAccessProc: TUnauthorizedAccessProc;
public
const clientVersion = '0.8.6';
const clientVersion = '0.8.8';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback);
......
......@@ -14,6 +14,7 @@ procedure ShowToast(const MessageText: string; const ToastType: string = 'succes
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
procedure ShowNotificationModal(msg: string);
procedure ShowAppDialog(const Msg: string; const Title: string = 'emT3 web app'; ReloadOnClose: Boolean = False; ButtonText: string = 'Close');
function NormalizeDateValue(const Value: string): string;
implementation
......@@ -281,5 +282,22 @@ begin
end;
function NormalizeDateValue(const Value: string): string;
var
normalizedValue: string;
begin
normalizedValue := Trim(Value);
if (normalizedValue = '') or
SameText(normalizedValue, '1899-12-30') or
SameText(normalizedValue, '12/30/1899') or
SameText(normalizedValue, '1899-12-30T00:00:00') or
SameText(normalizedValue, '1899-12-30 00:00:00') then
Result := ''
else
Result := normalizedValue;
end;
end.
......@@ -44,6 +44,20 @@ object FTaskItems: TFTaskItems
WidthPercent = 100.000000000000000000
OnClick = btnDeleteRowClick
end
object btnInsertRow: TWebButton
Left = 78
Top = 178
Width = 96
Height = 25
Caption = 'Insert Row'
ChildOrder = 3
ElementID = 'btn_insert_row'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnInsertRowClick
end
object xdwcTasks: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 506
......
......@@ -8,6 +8,7 @@
<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_insert_row" class="btn btn-sm btn-success">Insert Row</button>
<button id="btn_reload" class="btn btn-sm btn-primary">Reload</button>
</div>
</div>
......
......@@ -5,7 +5,7 @@ interface
uses
System.SysUtils, System.Classes,
JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
WEBLib.ExtCtrls, uNameManager,
WEBLib.ExtCtrls, uNameOffCanvas, uDropdownHelpers,
XData.Web.Client, XData.Web.Dataset,
Utils, Data.DB, XData.Web.JsonDataset, Vcl.Controls, Vcl.StdCtrls,
WEBLib.StdCtrls;
......@@ -30,12 +30,15 @@ type
xdwdsTasksitemNum: TIntegerField;
xdwdsTaskstaskItemId: TIntegerField;
btnDeleteRow: TWebButton;
btnInsertRow: TWebButton;
[async] procedure btnAddRowClick(Sender: TObject);
procedure btnReloadClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject);
[async] procedure btnDeleteRowClick(Sender: TObject);
[async] procedure btnInsertRowClick(Sender: TObject);
private
FTaskId: string;
FApplicationOptions: TJSArray;
FReportedByOptions: TJSArray;
FAssignedToOptions: TJSArray;
FStatusOptions: TJSArray;
......@@ -59,16 +62,14 @@ type
function HtmlEncode(const s: string): string;
procedure SetTotalRowsLabel(ARowCount: Integer);
procedure SetTaskLabel(const ATitle: string);
[async] procedure SaveRow(AIndex: Integer);
[async] procedure SaveField(AIndex: Integer; const AFieldName: string);
procedure EditorBlur(Event: TJSEvent);
[async] function AddTaskRow: Boolean;
[async] function AddTaskRowAtBottom: Boolean;
[async] function InsertTaskRowBelowSelection: Boolean;
[async] function AddTaskRowWithInsertAfter(const insertAfterItemNum, newItemNum: Integer): Boolean;
[async] function DeleteTaskRow: Boolean;
function ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
function GetOptionsForField(const AFieldName: string): TJSArray;
procedure FocusTrigger(const ATriggerId: string);
procedure DropdownItemClick(Event: TJSEvent);
procedure DropdownEditClick(Event: TJSEvent);
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
[async] procedure MoveTaskRow(AIndex: Integer; const newItemNum: Integer);
procedure ApplyPendingFocus;
procedure RowClick(Event: TJSEvent);
......@@ -80,11 +81,15 @@ type
[async] function AddAssignedName(const AName: string): TJSArray;
[async] function RenameAssignedName(const AOldName, ANewName: string): TJSArray;
[async] function DeleteAssignedName(const AName: string): TJSArray;
function ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
[async] procedure HandleAddAssignedName(const ARowIndex: Integer; const ANewName: string);
[async] procedure HandleRenameAssignedName(const AOldName, ANewName: string);
[async] procedure HandleDeleteAssignedName(const AName: string);
function FindAssignedOptionIgnoreCase(const AItems: TJSArray; const AName: string): string;
[async] function AddReportedName(const AName: string): TJSArray;
[async] function RenameReportedName(const AOldName, ANewName: string): TJSArray;
[async] function DeleteReportedName(const AName: string): TJSArray;
[async] function AddApplicationName(const AName: string): TJSArray;
[async] function RenameApplicationName(const AOldName, ANewName: string): TJSArray;
[async] function DeleteApplicationName(const AName: string): TJSArray;
[async] procedure HandleAddManagedName(const AFieldName: string; const ARowIndex: Integer; const ANewName: string);
[async] procedure HandleRenameManagedName(const AFieldName, AOldName, ANewName: string);
[async] procedure HandleDeleteManagedName(const AFieldName, AName: string);
public
end;
......@@ -103,6 +108,7 @@ procedure TFTaskItems.WebFormCreate(Sender: TObject);
begin
console.log('TFTaskItems.WebFormCreate fired');
FTaskId := Application.Parameters.Values['task_id'];
FApplicationOptions := TJSArray.new;
FReportedByOptions := TJSArray.new;
FAssignedToOptions := TJSArray.new;
FStatusOptions := TJSArray.new;
......@@ -112,23 +118,23 @@ begin
FNameManager := TNameManager.Create(
function(const AFieldName: string): TJSArray
begin
Result := GetOptionsForField(AFieldName);
Result := uDropdownHelpers.GetOptionsForField(AFieldName, FApplicationOptions, FReportedByOptions, FAssignedToOptions);
end,
procedure(const ATriggerId: string)
begin
FocusTrigger(ATriggerId);
uDropdownHelpers.FocusTrigger(ATriggerId);
end,
procedure(const ARowIndex: Integer; const ANewName: string)
procedure(const AFieldName: string; const ARowIndex: Integer; const ANewName: string)
begin
HandleAddAssignedName(ARowIndex, ANewName);
HandleAddManagedName(AFieldName, ARowIndex, ANewName);
end,
procedure(const AOldName, ANewName: string)
procedure(const AFieldName, AOldName, ANewName: string)
begin
HandleRenameAssignedName(AOldName, ANewName);
HandleRenameManagedName(AFieldName, AOldName, ANewName);
end,
procedure(const AName: string)
procedure(const AFieldName, AName: string)
begin
HandleDeleteAssignedName(AName);
HandleDeleteManagedName(AFieldName, AName);
end
);
......@@ -140,6 +146,7 @@ begin
Exit;
end;
btnInsertRow.Enabled := False;
btnAddRow.Enabled := False;
btnDeleteRow.Enabled := False;
......@@ -253,7 +260,7 @@ begin
xdwdsTasks.Post;
el.setAttribute('data-unsaved-data', '1');
SaveRow(idx);
SaveField(idx, fieldName);
end;
......@@ -342,7 +349,7 @@ end;
begin
Utils.ShowSpinner('spinner');
try
if await(AddTaskRow) then
if await(AddTaskRowAtBottom) then
begin
CaptureTableScroll;
LoadTasks(FTaskId);
......@@ -381,6 +388,7 @@ begin
begin
FSelectedTaskItemId := 0;
FSelectedTaskId := 0;
btnInsertRow.Enabled := False;
btnDeleteRow.Enabled := False;
FPendingFocusItemNum := deletedItemNum;
......@@ -394,6 +402,20 @@ begin
end;
end;
[async] procedure TFTaskItems.btnInsertRowClick(Sender: TObject);
begin
Utils.ShowSpinner('spinner');
try
if await(InsertTaskRowBelowSelection) then
begin
CaptureTableScroll;
LoadTasks(FTaskId);
end;
finally
Utils.HideSpinner('spinner');
end;
end;
procedure TFTaskItems.RowClick(Event: TJSEvent);
var
rowEl: TJSHTMLElement;
......@@ -410,6 +432,7 @@ begin
FSelectedTaskItemId := StrToIntDef(taskItemIdStr, 0);
FSelectedTaskId := StrToIntDef(taskIdStr, 0);
btnInsertRow.Enabled := FSelectedTaskItemId > 0;
btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
ApplySelectedRowState;
end;
......@@ -429,43 +452,76 @@ begin
end;
[async] function TFTaskItems.AddTaskRow: Boolean;
[async] function TFTaskItems.AddTaskRowAtBottom: Boolean;
var
response: TXDataClientResponse;
insertAfterItemNum: Integer;
newItemNum: Integer;
maxItemNum: Integer;
begin
maxItemNum := 0;
if xdwdsTasks.Active then
begin
xdwdsTasks.First;
while not xdwdsTasks.Eof do
begin
if xdwdsTasksitemNum.AsInteger > maxItemNum then
maxItemNum := xdwdsTasksitemNum.AsInteger;
xdwdsTasks.Next;
end;
end;
Result := await(AddTaskRowWithInsertAfter(0, maxItemNum + 1));
end;
[async] function TFTaskItems.InsertTaskRowBelowSelection: Boolean;
var
insertAfterItemNum: Integer;
begin
Result := False;
if FTaskId = '' then
if FSelectedTaskItemId <= 0 then
begin
Utils.ShowErrorModal('Missing task_id. Please reopen from emt3.');
Utils.ShowErrorModal('Select a row first.');
Exit;
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
begin
insertAfterItemNum := xdwdsTasksitemNum.AsInteger;
Break;
end;
xdwdsTasks.Next;
end;
end;
if insertAfterItemNum > 0 then
newItemNum := insertAfterItemNum + 1
else
newItemNum := maxItemNum + 1;
if insertAfterItemNum <= 0 then
begin
Utils.ShowErrorModal('Unable to find selected row.');
Exit;
end;
Result := await(AddTaskRowWithInsertAfter(insertAfterItemNum, insertAfterItemNum + 1));
end;
[async] function TFTaskItems.AddTaskRowWithInsertAfter(const insertAfterItemNum, newItemNum: Integer): Boolean;
var
response: TXDataClientResponse;
begin
Result := False;
if FTaskId = '' then
begin
Utils.ShowErrorModal('Missing task_id. Please reopen from emt3.');
Exit;
end;
try
response := await(xdwcTasks.RawInvokeAsync(
......@@ -575,6 +631,7 @@ begin
rowCount := StrToIntDef(string(resultObj['count']), 0);
SetTotalRowsLabel(rowCount);
FApplicationOptions := ExtractOptionNames(TJSArray(resultObj['applicationOptions']));
FReportedByOptions := ExtractOptionNames(TJSArray(resultObj['reportedByOptions']));
FAssignedToOptions := ExtractOptionNames(TJSArray(resultObj['assignedToOptions']));
FStatusOptions := ExtractCodeDescs(TJSArray(resultObj['statusOptions']));
......@@ -588,6 +645,7 @@ begin
xdwdsTasks.Open;
btnAddRow.Enabled := True;
btnInsertRow.Enabled := FSelectedTaskItemId > 0;
btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
RenderTable;
......@@ -623,13 +681,11 @@ var
Result := '<td class="align-top wrap-cell">' + s + '</td>';
end;
function TextInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string;
function TextInput(const FieldName, Value: string; const AIdx: Integer): string;
var
w: string;
begin
w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"';
Result :=
'<input class="form-control form-control-sm cell-input task-editor w-100" ' +
......@@ -655,18 +711,19 @@ var
'value="' + IntToStr(Value) + '">';
end;
function DateInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string;
function DateInput(const FieldName, Value: string; const AIdx: Integer): string;
var
w: string;
dateValue: string;
begin
w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"';
dateValue := Utils.NormalizeDateValue(Value);
Result :=
'<input type="date" class="form-control form-control-sm cell-input task-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'value="' + HtmlEncode(Value) + '"' + w + '>';
'value="' + HtmlEncode(dateValue) + '"' + w + '>';
end;
function SelectList(const FieldName, Current: string; const AIdx: Integer; const Items: TJSArray; const AllowEdit: Boolean): string;
......@@ -674,13 +731,21 @@ var
i: Integer;
itemText: string;
triggerId: string;
buttonClass: string;
begin
triggerId := 'task_dd_' + FieldName + '_' + IntToStr(AIdx);
buttonClass := 'btn btn-sm border w-100 d-flex justify-content-between align-items-center text-start task-dd-toggle ';
if SameText(FieldName, 'status') then
buttonClass := buttonClass + GetTaskStatusClass(Current)
else
buttonClass := buttonClass + 'btn-light';
Result :=
'<div class="dropdown w-100">' +
'<button id="' + triggerId + '" class="btn btn-sm btn-light border w-100 d-flex justify-content-between align-items-center text-start task-dd-toggle" ' +
'type="button" data-bs-toggle="dropdown" aria-expanded="false">' +
'<button id="' + triggerId + '" class="' + buttonClass + '" ' +
'type="button" data-bs-toggle="dropdown" aria-expanded="false" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '">' +
'<span class="task-dd-label text-truncate">' + HtmlEncode(Current) + '</span>' +
'<span class="dropdown-toggle dropdown-toggle-split border-0 ms-2"></span>' +
'</button>' +
......@@ -721,31 +786,6 @@ var
'</div>';
end;
function StatusSelect(const Current: string; const AIdx: Integer): string;
var
i: Integer;
statusText: string;
sel: string;
begin
Result :=
'<select class="form-select form-select-sm task-select" data-idx="' + IntToStr(AIdx) + '" data-field="status">';
Result := Result + '<option value=""></option>'; //Note: This adds the blank option on top
if Assigned(FStatusOptions) then
for i := 0 to FStatusOptions.length - 1 do
begin
statusText := string(FStatusOptions[i]);
sel := '';
if SameText(Current, statusText) then
sel := ' selected';
Result := Result + '<option value="' + HtmlEncode(statusText) + '"' + sel + '>' + HtmlEncode(statusText) + '</option>';
end;
Result := Result + '</select>';
end;
begin
host := TJSHTMLElement(document.getElementById('tasks_table_host'));
if not Assigned(host) then
......@@ -754,12 +794,12 @@ begin
html :=
'<div class="tasks-vscroll">' +
'<div class="tasks-hscroll">' +
'<table class="table table-sm table-bordered align-middle mb-0" style="min-width: 1700px;">' +
'<table class="table table-sm table-bordered align-middle mb-0 tasks-table" style="min-width: 1700px; table-layout: fixed;">' +
'<colgroup>' +
'<col style="width:40px">' + // Item Num
'<col style="width:200px">' + // App
'<col style="width:90px">' + // Version
'<col style="width:120px">' + // Date
'<col style="width:140px">' + // Date
'<col style="width:120px">' + // Reported
'<col style="width:120px">' + // Assigned
'<col style="width:195px">' + // Status
......@@ -778,7 +818,7 @@ begin
Th('Status') +
Th('Status Date') +
Th('Form') +
Th('Issue') +
Th('Issue/To-Do') +
Th('Notes') +
'</tr></thead><tbody>';
......@@ -789,14 +829,14 @@ begin
html := html +
'<tr class="task-row-selectable" data-task-item-id="' + IntToStr(xdwdsTaskstaskItemId.AsInteger) + '" data-task-id="' + xdwdsTaskstaskId.AsString + '" data-item-num="' + IntToStr(xdwdsTasksitemNum.AsInteger) + '">' +
TdNowrap(ItemNumInput(xdwdsTasksitemNum.AsInteger, rowIdx)) +
TdNowrap(TextInput('application', xdwdsTasksapplication.AsString, rowIdx, 180)) +
TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx, 80)) +
TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx, 110)) +
TdNowrap(SelectList('reportedBy', xdwdsTasksreportedBy.AsString, rowIdx, FReportedByOptions, False)) +
TdNowrap(SelectList('application', xdwdsTasksapplication.AsString, rowIdx, FApplicationOptions, True)) +
TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx)) +
TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx)) +
TdNowrap(SelectList('reportedBy', xdwdsTasksreportedBy.AsString, rowIdx, FReportedByOptions, True)) +
TdNowrap(SelectList('assignedTo', xdwdsTasksassignedTo.AsString, rowIdx, FAssignedToOptions, True)) +
TdNowrap(StatusSelect(xdwdsTasksstatus.AsString, rowIdx)) +
TdNowrap(DateInput('statusDate', xdwdsTasksstatusDate.AsString, rowIdx, 110)) +
TdNowrap(TextInput('formSection', xdwdsTasksformSection.AsString, rowIdx, 160)) +
TdNowrap(SelectList('status', xdwdsTasksstatus.AsString, rowIdx, FStatusOptions, False)) +
TdNowrap(DateInput('statusDate', xdwdsTasksstatusDate.AsString, rowIdx)) +
TdNowrap(TextInput('formSection', xdwdsTasksformSection.AsString, rowIdx)) +
TdWrap(TextArea('issue', xdwdsTasksissue.AsString, rowIdx)) +
TdWrap(TextArea('notes', xdwdsTasksnotes.AsString, rowIdx)) +
'</tr>';
......@@ -823,12 +863,16 @@ begin
(function(){
const host = document.getElementById('tasks_table_host');
if(!host) return;
const table = host.querySelector('table');
if(!table) return;
const ths = table.querySelectorAll('thead th');
ths.forEach(th => {
const cols = table.querySelectorAll('colgroup col');
ths.forEach((th, index) => {
th.classList.add('th-resize');
if(th.querySelector('.th-resize-handle')) return;
if (th.querySelector('.th-resize-handle')) return;
const handle = document.createElement('div');
handle.className = 'th-resize-handle';
......@@ -836,17 +880,27 @@ begin
handle.addEventListener('mousedown', function(e){
e.preventDefault();
e.stopPropagation();
const startX = e.clientX;
const startW = th.getBoundingClientRect().width;
function onMove(ev){
const w = Math.max(40, startW + (ev.clientX - startX));
const w = Math.max(20, startW + (ev.clientX - startX));
th.style.width = w + 'px';
th.style.minWidth = '0';
if (cols && cols[index]) {
cols[index].style.width = w + 'px';
}
}
function onUp(){
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
......@@ -889,7 +943,7 @@ begin
end;
console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName);
SaveRow(idx);
SaveField(idx, fieldName);
end;
......@@ -934,14 +988,13 @@ begin
end;
[async] procedure TFTaskItems.SaveRow(AIndex: Integer);
[async] procedure TFTaskItems.SaveField(AIndex: Integer; const AFieldName: string);
const
// Note: Use this to manipulate saving to the server or not for testing
ENABLE_SERVER_SAVE = True;
var
response: TXDataClientResponse;
payload: TJSObject;
payloadJson: string;
fieldValue: string;
begin
if not xdwdsTasks.Active then
Exit;
......@@ -950,35 +1003,47 @@ begin
if xdwdsTasks.Eof then
Exit;
payload := TJSObject.new;
fieldValue := '';
if SameText(AFieldName, 'application') then
fieldValue := xdwdsTasksapplication.AsString
else if SameText(AFieldName, 'version') then
fieldValue := xdwdsTasksversion.AsString
else if SameText(AFieldName, 'taskDate') then
fieldValue := xdwdsTaskstaskDate.AsString
else if SameText(AFieldName, 'reportedBy') then
fieldValue := xdwdsTasksreportedBy.AsString
else if SameText(AFieldName, 'assignedTo') then
fieldValue := xdwdsTasksassignedTo.AsString
else if SameText(AFieldName, 'status') then
fieldValue := xdwdsTasksstatus.AsString
else if SameText(AFieldName, 'statusDate') then
fieldValue := xdwdsTasksstatusDate.AsString
else if SameText(AFieldName, 'formSection') then
fieldValue := xdwdsTasksformSection.AsString
else if SameText(AFieldName, 'issue') then
fieldValue := xdwdsTasksissue.AsString
else if SameText(AFieldName, 'notes') then
fieldValue := xdwdsTasksnotes.AsString
else
Exit;
payload := TJSObject.new;
payload['taskItemId'] := xdwdsTaskstaskItemId.AsInteger;
payload['taskId'] := xdwdsTaskstaskId.AsString;
payload['application'] := xdwdsTasksapplication.AsString;
payload['version'] := xdwdsTasksversion.AsString;
payload['taskDate'] := xdwdsTaskstaskDate.AsString;
payload['reportedBy'] := xdwdsTasksreportedBy.AsString;
payload['assignedTo'] := xdwdsTasksassignedTo.AsString;
payload['status'] := xdwdsTasksstatus.AsString;
payload['statusDate'] := xdwdsTasksstatusDate.AsString;
payload['formSection'] := xdwdsTasksformSection.AsString;
payload['issue'] := xdwdsTasksissue.AsString;
payload['notes'] := xdwdsTasksnotes.AsString;
payloadJson := string(TJSJSON.stringify(payload));
console.log('SaveRow: idx=' + IntToStr(AIndex) + ' payload=' + payloadJson);
payload['fieldName'] := AFieldName;
payload['value'] := fieldValue;
console.log('SaveField: idx=' + IntToStr(AIndex) + ' field=' + AFieldName + ' value=' + fieldValue);
if not ENABLE_SERVER_SAVE then
Exit;
try
response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskRow', [payload]));
console.log('SaveRow: response=' + string(TJSJSON.stringify(response.Result)));
response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskItemField', [payload]));
console.log('SaveField: response=' + string(TJSJSON.stringify(response.Result)));
except
on E: EXDataClientRequestException do
begin
console.log('SaveRow ERROR: ' + E.ErrorResult.ErrorMessage);
console.log('SaveField ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
end;
......@@ -1010,73 +1075,14 @@ begin
end;
end;
function TFTaskItems.ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['name']));
end;
end;
function TFTaskItems.ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['codeDesc']));
end;
end;
function TFTaskItems.GetOptionsForField(const AFieldName: string): TJSArray;
begin
if SameText(AFieldName, 'reportedBy') then
Result := FReportedByOptions
else if SameText(AFieldName, 'assignedTo') then
Result := FAssignedToOptions
else
Result := nil;
end;
procedure TFTaskItems.FocusTrigger(const ATriggerId: string);
var
el: TJSHTMLElement;
begin
if ATriggerId = '' then
Exit;
el := TJSHTMLElement(document.getElementById(ATriggerId));
if Assigned(el) then
el.focus;
end;
procedure TFTaskItems.DropdownItemClick(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr, fieldName, newVal, triggerId: string;
btn: TJSHTMLElement;
labelEl: TJSHTMLElement;
begin
if not xdwdsTasks.Active then
Exit;
......@@ -1104,18 +1110,19 @@ begin
if triggerId <> '' then
begin
asm
var btn = document.getElementById(triggerId);
if (btn) {
var labelEl = btn.querySelector('.task-dd-label');
if (labelEl) {
labelEl.textContent = newVal;
}
}
btn := TJSHTMLElement(document.getElementById(triggerId));
if Assigned(btn) then
begin
labelEl := TJSHTMLElement(btn.querySelector('.task-dd-label'));
if Assigned(labelEl) then
labelEl.textContent := newVal;
if SameText(fieldName, 'status') then
ApplyTaskStatusClass(btn, newVal);
end;
end;
SaveRow(idx);
SaveField(idx, fieldName);
end;
......@@ -1138,7 +1145,7 @@ begin
if (idx < 0) or (fieldName = '') then
Exit;
if not SameText(fieldName, 'assignedTo') then
if not (SameText(fieldName, 'assignedTo') or SameText(fieldName, 'application') or SameText(fieldName, 'reportedBy')) then
Exit;
FNameManager.OpenManager(fieldName, idx, triggerId);
......@@ -1198,12 +1205,16 @@ begin
FSelectedTaskItemId := StrToIntDef(taskItemIdStr, 0);
FSelectedTaskId := StrToIntDef(taskIdStr, 0);
btnInsertRow.Enabled := FSelectedTaskItemId > 0;
btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
ApplySelectedRowState;
asm
rowEl.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' });
end;
end;
asm
el.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' });
el.focus();
end;
end;
......@@ -1235,14 +1246,6 @@ begin
end;
function TFTaskItems.ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['assignedToOptions']));
end;
[async] function TFTaskItems.AddAssignedName(const AName: string): TJSArray;
var
response: TXDataClientResponse;
......@@ -1321,36 +1324,32 @@ begin
end;
end;
function TFTaskItems.FindAssignedOptionIgnoreCase(const AItems: TJSArray; const AName: string): string;
var
i: Integer;
itemText: string;
begin
Result := '';
if not Assigned(AItems) then
Exit;
for i := 0 to AItems.length - 1 do
begin
itemText := string(AItems[i]);
if SameText(itemText, Trim(AName)) then
Exit(itemText);
end;
end;
[async] procedure TFTaskItems.HandleAddAssignedName(const ARowIndex: Integer; const ANewName: string);
[async] procedure TFTaskItems.HandleAddManagedName(const AFieldName: string; const ARowIndex: Integer; const ANewName: string);
var
newOptions: TJSArray;
resolvedName: string;
begin
newOptions := await(AddAssignedName(ANewName));
if SameText(AFieldName, 'application') then
newOptions := await(AddApplicationName(ANewName))
else if SameText(AFieldName, 'assignedTo') then
newOptions := await(AddAssignedName(ANewName))
else if SameText(AFieldName, 'reportedBy') then
newOptions := await(AddReportedName(ANewName))
else
Exit;
if not Assigned(newOptions) then
Exit;
if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
resolvedName := FindAssignedOptionIgnoreCase(FAssignedToOptions, ANewName);
resolvedName := FindOptionIgnoreCase(newOptions, ANewName);
if resolvedName = '' then
resolvedName := Trim(ANewName);
......@@ -1362,39 +1361,224 @@ begin
Exit;
xdwdsTasks.Edit;
xdwdsTasksassignedTo.AsString := resolvedName;
xdwdsTasks.FieldByName(AFieldName).AsString := resolvedName;
xdwdsTasks.Post;
RenderTable;
await(SaveRow(ARowIndex));
await(SaveField(ARowIndex, AFieldName));
end;
[async] procedure TFTaskItems.HandleRenameAssignedName(const AOldName, ANewName: string);
[async] procedure TFTaskItems.HandleRenameManagedName(const AFieldName, AOldName, ANewName: string);
var
newOptions: TJSArray;
begin
newOptions := await(RenameAssignedName(AOldName, ANewName));
if SameText(AFieldName, 'application') then
newOptions := await(RenameApplicationName(AOldName, ANewName))
else if SameText(AFieldName, 'assignedTo') then
newOptions := await(RenameAssignedName(AOldName, ANewName))
else if SameText(AFieldName, 'reportedBy') then
newOptions := await(RenameReportedName(AOldName, ANewName))
else
Exit;
if not Assigned(newOptions) then
Exit;
if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
CaptureTableScroll;
LoadTasks(FTaskId);
end;
[async] procedure TFTaskItems.HandleDeleteAssignedName(const AName: string);
[async] procedure TFTaskItems.HandleDeleteManagedName(const AFieldName, AName: string);
var
newOptions: TJSArray;
begin
newOptions := await(DeleteAssignedName(AName));
if SameText(AFieldName, 'application') then
newOptions := await(DeleteApplicationName(AName))
else if SameText(AFieldName, 'assignedTo') then
newOptions := await(DeleteAssignedName(AName))
else if SameText(AFieldName, 'reportedBy') then
newOptions := await(DeleteReportedName(AName))
else
Exit;
if not Assigned(newOptions) then
Exit;
if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
CaptureTableScroll;
LoadTasks(FTaskId);
end;
[async] function TFTaskItems.AddApplicationName(const AName: string): TJSArray;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
Result := nil;
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.AddApplicationName',
[FTaskId, Trim(AName)]
));
resultObj := TJSObject(response.Result);
Result := ExtractApplicationOptionNames(resultObj);
FApplicationOptions := Result;
except
on E: EXDataClientRequestException do
begin
console.log('AddApplicationName ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
end;
[async] function TFTaskItems.RenameApplicationName(const AOldName, ANewName: string): TJSArray;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
Result := nil;
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.RenameApplicationName',
[FTaskId, Trim(AOldName), Trim(ANewName)]
));
resultObj := TJSObject(response.Result);
Result := ExtractApplicationOptionNames(resultObj);
FApplicationOptions := Result;
except
on E: EXDataClientRequestException do
begin
console.log('RenameApplicationName ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
end;
[async] function TFTaskItems.DeleteApplicationName(const AName: string): TJSArray;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
Result := nil;
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.DeleteApplicationName',
[FTaskId, Trim(AName)]
));
resultObj := TJSObject(response.Result);
Result := ExtractApplicationOptionNames(resultObj);
FApplicationOptions := Result;
except
on E: EXDataClientRequestException do
begin
console.log('DeleteApplicationName ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
end;
[async] function TFTaskItems.AddReportedName(const AName: string): TJSArray;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
Result := nil;
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.AddReportedName',
[FTaskId, Trim(AName)]
));
resultObj := TJSObject(response.Result);
Result := ExtractReportedOptionNames(resultObj);
FReportedByOptions := Result;
except
on E: EXDataClientRequestException do
begin
console.log('AddReportedName ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
end;
[async] function TFTaskItems.RenameReportedName(const AOldName, ANewName: string): TJSArray;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
Result := nil;
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.RenameReportedName',
[FTaskId, Trim(AOldName), Trim(ANewName)]
));
resultObj := TJSObject(response.Result);
Result := ExtractReportedOptionNames(resultObj);
FReportedByOptions := Result;
except
on E: EXDataClientRequestException do
begin
console.log('RenameReportedName ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
end;
[async] function TFTaskItems.DeleteReportedName(const AName: string): TJSArray;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
Result := nil;
try
response := await(xdwcTasks.RawInvokeAsync(
'IApiService.DeleteReportedName',
[FTaskId, Trim(AName)]
));
resultObj := TJSObject(response.Result);
Result := ExtractReportedOptionNames(resultObj);
FReportedByOptions := Result;
except
on E: EXDataClientRequestException do
begin
console.log('DeleteReportedName ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
end;
......
......@@ -62,6 +62,9 @@ input[data-field="itemNum"] {
top: 0;
z-index: 2;
background: var(--bs-body-bg);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tasks-vscroll thead th.th-resize {
......@@ -86,5 +89,155 @@ span.card {
user-select: none;
}
.tasks-table {
table-layout: fixed;
}
.tasks-table th {
overflow: hidden;
}
.tasks-table td {
overflow: visible;
}
.nowrap-cell,
.wrap-cell {
overflow: visible;
}
.tasks-table .dropdown,
.task-dd-toggle,
.task-dd-label,
.cell-input,
.cell-textarea {
min-width: 0;
}
.dropdown-menu {
z-index: 1055;
}
.task-dd-toggle.status-cannot-duplicate {
--bs-btn-color: #41464b;
--bs-btn-bg: #e2e3e5;
--bs-btn-border-color: #c4c8cb;
--bs-btn-hover-color: #41464b;
--bs-btn-hover-bg: #d3d4d5;
--bs-btn-hover-border-color: #b9bcc0;
--bs-btn-active-color: #41464b;
--bs-btn-active-bg: #c6c7c8;
--bs-btn-active-border-color: #b9bcc0;
}
.task-dd-toggle.status-cannot-test {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-future-enhancement {
--bs-btn-color: #055160;
--bs-btn-bg: #cff4fc;
--bs-btn-border-color: #9eeaf9;
--bs-btn-hover-color: #055160;
--bs-btn-hover-bg: #b6effb;
--bs-btn-hover-border-color: #86e5f8;
--bs-btn-active-color: #055160;
--bs-btn-active-bg: #9eeaf9;
--bs-btn-active-border-color: #74dff6;
}
.task-dd-toggle.status-fixed-verified {
--bs-btn-color: #0f5132;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #0f5132;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #0f5132;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-fixed {
--bs-btn-color: #146c43;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #146c43;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #146c43;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-investigating {
--bs-btn-color: #664d03;
--bs-btn-bg: #fff3cd;
--bs-btn-border-color: #ffe69c;
--bs-btn-hover-color: #664d03;
--bs-btn-hover-bg: #ffecb5;
--bs-btn-hover-border-color: #ffdf7e;
--bs-btn-active-color: #664d03;
--bs-btn-active-bg: #ffe69c;
--bs-btn-active-border-color: #ffd966;
}
.task-dd-toggle.status-not-fixed {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-non-issue {
--bs-btn-color: #432874;
--bs-btn-bg: #e2d9f3;
--bs-btn-border-color: #cbbbe8;
--bs-btn-hover-color: #432874;
--bs-btn-hover-bg: #d6caee;
--bs-btn-hover-border-color: #bea9e2;
--bs-btn-active-color: #432874;
--bs-btn-active-bg: #cbbbe8;
--bs-btn-active-border-color: #b89ddd;
}
.task-dd-toggle.status-possibly-a-problem {
--bs-btn-color: #7a3e00;
--bs-btn-bg: #ffe5d0;
--bs-btn-border-color: #f7c79d;
--bs-btn-hover-color: #7a3e00;
--bs-btn-hover-bg: #ffd7b8;
--bs-btn-hover-border-color: #f2ba88;
--bs-btn-active-color: #7a3e00;
--bs-btn-active-bg: #f7c79d;
--bs-btn-active-border-color: #eeaf69;
}
.task-dd-toggle.status-default {
--bs-btn-color: #212529;
--bs-btn-bg: #f8f9fa;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #212529;
--bs-btn-hover-bg: #e9ecef;
--bs-btn-hover-border-color: #ced4da;
--bs-btn-active-color: #212529;
--bs-btn-active-bg: #dee2e6;
--bs-btn-active-border-color: #ced4da;
}
......@@ -12,7 +12,8 @@ uses
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
Utils in 'Utils.pas',
View.TaskItems in 'View.TaskItems.pas' {FTaskItems: TWebForm} {*.html},
uNameManager in 'uNameManager.pas';
uNameOffCanvas in 'uNameOffCanvas.pas',
uDropdownHelpers in 'uDropdownHelpers.pas';
{$R *.res}
......
......@@ -94,13 +94,13 @@
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.8.6.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.8.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.8.8.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.8.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>8</VerInfo_MinorVer>
<VerInfo_Release>6</VerInfo_Release>
<VerInfo_Release>8</VerInfo_Release>
<TMSWebBrowser>5</TMSWebBrowser>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
</PropertyGroup>
......@@ -142,7 +142,8 @@
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="uNameManager.pas"/>
<DCCReference Include="uNameOffCanvas.pas"/>
<DCCReference Include="uDropdownHelpers.pas"/>
<None Include="index.html"/>
<None Include="css\app.css"/>
<None Include="config\config.json"/>
......
unit uDropdownHelpers;
interface
uses
System.SysUtils, System.Classes, JS, Web,
WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs;
function GetTaskStatusClass(const AStatus: string): string;
procedure ClearTaskStatusClasses(const AElement: TJSHTMLElement);
procedure ApplyTaskStatusClass(const AElement: TJSHTMLElement; const AStatus: string);
function ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
function ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
function ExtractReportedOptionNames(const ResponseResult: TJSObject): TJSArray;
function ExtractApplicationOptionNames(const ResponseResult: TJSObject): TJSArray;
function FindOptionIgnoreCase(const AItems: TJSArray; const AName: string): string;
function GetOptionsForField(const AFieldName: string; const AApplicationOptions, AReportedByOptions, AAssignedToOptions: TJSArray): TJSArray;
procedure FocusTrigger(const ATriggerId: string);
implementation
function GetTaskStatusClass(const AStatus: string): string;
var
statusText: string;
begin
statusText := Trim(AStatus);
if SameText(statusText, 'Cannot Duplicate') then
Result := 'status-cannot-duplicate'
else if SameText(statusText, 'Cannot Test') then
Result := 'status-cannot-test'
else if SameText(statusText, 'Future Enhancement') then
Result := 'status-future-enhancement'
else if SameText(statusText, 'Fixed - Verified') then
Result := 'status-fixed-verified'
else if SameText(statusText, 'Fixed') then
Result := 'status-fixed'
else if SameText(statusText, 'Investigating') then
Result := 'status-investigating'
else if SameText(statusText, 'Not Fixed') then
Result := 'status-not-fixed'
else if SameText(statusText, 'Non-Issue') then
Result := 'status-non-issue'
else if SameText(statusText, 'Possibly a Problem') then
Result := 'status-possibly-a-problem'
else
Result := 'status-default';
end;
procedure ClearTaskStatusClasses(const AElement: TJSHTMLElement);
begin
if not Assigned(AElement) then
Exit;
AElement.classList.remove('status-cannot-duplicate');
AElement.classList.remove('status-cannot-test');
AElement.classList.remove('status-future-enhancement');
AElement.classList.remove('status-fixed-verified');
AElement.classList.remove('status-fixed');
AElement.classList.remove('status-investigating');
AElement.classList.remove('status-not-fixed');
AElement.classList.remove('status-non-issue');
AElement.classList.remove('status-possibly-a-problem');
AElement.classList.remove('status-default');
end;
procedure ApplyTaskStatusClass(const AElement: TJSHTMLElement; const AStatus: string);
begin
if not Assigned(AElement) then
Exit;
ClearTaskStatusClasses(AElement);
AElement.classList.add(GetTaskStatusClass(AStatus));
end;
function ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['name']));
end;
end;
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['codeDesc']));
end;
end;
function ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['assignedToOptions']));
end;
function ExtractReportedOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['reportedByOptions']));
end;
function ExtractApplicationOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['applicationOptions']));
end;
function FindOptionIgnoreCase(const AItems: TJSArray; const AName: string): string;
var
i: Integer;
itemText: string;
begin
Result := '';
if not Assigned(AItems) then
Exit;
for i := 0 to AItems.length - 1 do
begin
itemText := string(AItems[i]);
if SameText(itemText, Trim(AName)) then
Exit(itemText);
end;
end;
function GetOptionsForField(const AFieldName: string; const AApplicationOptions, AReportedByOptions, AAssignedToOptions: TJSArray): TJSArray;
begin
if SameText(AFieldName, 'application') then
Result := AApplicationOptions
else if SameText(AFieldName, 'reportedBy') then
Result := AReportedByOptions
else if SameText(AFieldName, 'assignedTo') then
Result := AAssignedToOptions
else
Result := nil;
end;
procedure FocusTrigger(const ATriggerId: string);
var
el: TJSHTMLElement;
begin
if ATriggerId = '' then
Exit;
el := TJSHTMLElement(document.getElementById(ATriggerId));
if Assigned(el) then
el.focus;
end;
end.
unit uNameManager;
unit uNameOffCanvas;
interface
......@@ -8,9 +8,9 @@ uses
type
TGetOptionsEvent = reference to function(const AFieldName: string): TJSArray;
TFocusTriggerEvent = reference to procedure(const ATriggerId: string);
TAddAssignedNameEvent = reference to procedure(const ARowIndex: Integer; const ANewName: string);
TRenameAssignedNameEvent = reference to procedure(const AOldName, ANewName: string);
TDeleteAssignedNameEvent = reference to procedure(const AName: string);
TAddManagedNameEvent = reference to procedure(const AFieldName: string; const ARowIndex: Integer; const ANewName: string);
TRenameManagedNameEvent = reference to procedure(const AFieldName, AOldName, ANewName: string);
TDeleteManagedNameEvent = reference to procedure(const AFieldName, AName: string);
TNameManager = class
private
......@@ -20,9 +20,9 @@ type
FCurrentEditName: string;
FGetOptions: TGetOptionsEvent;
FFocusTrigger: TFocusTriggerEvent;
FAddAssignedName: TAddAssignedNameEvent;
FRenameAssignedName: TRenameAssignedNameEvent;
FDeleteAssignedName: TDeleteAssignedNameEvent;
FAddManagedName: TAddManagedNameEvent;
FRenameManagedName: TRenameManagedNameEvent;
FDeleteManagedName: TDeleteManagedNameEvent;
procedure AddAnotherClick(Event: TJSEvent);
procedure SaveClick(Event: TJSEvent);
procedure NameInputKeyDown(Event: TJSEvent);
......@@ -40,9 +40,9 @@ type
constructor Create(
const AGetOptions: TGetOptionsEvent;
const AFocusTrigger: TFocusTriggerEvent;
const AAddAssignedName: TAddAssignedNameEvent;
const ARenameAssignedName: TRenameAssignedNameEvent;
const ADeleteAssignedName: TDeleteAssignedNameEvent
const AAddManagedName: TAddManagedNameEvent;
const ARenameManagedName: TRenameManagedNameEvent;
const ADeleteManagedName: TDeleteManagedNameEvent
);
procedure BindControls;
......@@ -70,17 +70,17 @@ end;
constructor TNameManager.Create(
const AGetOptions: TGetOptionsEvent;
const AFocusTrigger: TFocusTriggerEvent;
const AAddAssignedName: TAddAssignedNameEvent;
const ARenameAssignedName: TRenameAssignedNameEvent;
const ADeleteAssignedName: TDeleteAssignedNameEvent
const AAddManagedName: TAddManagedNameEvent;
const ARenameManagedName: TRenameManagedNameEvent;
const ADeleteManagedName: TDeleteManagedNameEvent
);
begin
inherited Create;
FGetOptions := AGetOptions;
FFocusTrigger := AFocusTrigger;
FAddAssignedName := AAddAssignedName;
FRenameAssignedName := ARenameAssignedName;
FDeleteAssignedName := ADeleteAssignedName;
FAddManagedName := AAddManagedName;
FRenameManagedName := ARenameManagedName;
FDeleteManagedName := ADeleteManagedName;
FCurrentField := '';
FCurrentRowIndex := -1;
FCurrentTriggerId := '';
......@@ -257,18 +257,18 @@ begin
Exit;
end;
if not SameText(FCurrentField, 'assignedTo') then
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit;
if FCurrentEditName <> '' then
begin
if Assigned(FRenameAssignedName) then
FRenameAssignedName(FCurrentEditName, newName);
if Assigned(FRenameManagedName) then
FRenameManagedName(FCurrentField, FCurrentEditName, newName);
end
else
begin
if Assigned(FAddAssignedName) then
FAddAssignedName(FCurrentRowIndex, newName);
if Assigned(FAddManagedName) then
FAddManagedName(FCurrentField, FCurrentRowIndex, newName);
end;
CloseManager;
......@@ -356,7 +356,7 @@ begin
Event.preventDefault;
Event.stopPropagation;
if not SameText(FCurrentField, 'assignedTo') then
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit;
el := TJSHTMLElement(Event.currentTarget);
......@@ -383,7 +383,7 @@ begin
Event.preventDefault;
Event.stopPropagation;
if not SameText(FCurrentField, 'assignedTo') then
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit;
el := TJSHTMLElement(Event.currentTarget);
......@@ -391,8 +391,8 @@ begin
if nameToDelete = '' then
Exit;
if Assigned(FDeleteAssignedName) then
FDeleteAssignedName(nameToDelete);
if Assigned(FDeleteManagedName) then
FDeleteManagedName(FCurrentField, nameToDelete);
CloseManager;
end;
......@@ -401,7 +401,7 @@ procedure TNameManager.OpenManager(const AFieldName: string; const ARowIndex: In
var
titleEl: TJSHTMLElement;
begin
if not SameText(AFieldName, 'assignedTo') then
if not (SameText(AFieldName, 'assignedTo') or SameText(AFieldName, 'application') or SameText(AFieldName, 'reportedBy')) then
Exit;
FCurrentField := AFieldName;
......@@ -411,7 +411,14 @@ begin
titleEl := GetElement('nm_title');
if Assigned(titleEl) then
begin
if SameText(AFieldName, 'application') then
titleEl.innerHTML := 'Manage Application'
else if SameText(AFieldName, 'reportedBy') then
titleEl.innerHTML := 'Manage Reported By'
else
titleEl.innerHTML := 'Manage Assigned To';
end;
ResetInputArea;
RenderExistingList;
......
object ApiDatabase: TApiDatabase
OnCreate = DataModuleCreate
Height = 453
Width = 641
Height = 475
Width = 996
object ucETaskApi: TUniConnection
AutoCommit = False
ProviderName = 'MySQL'
Database = 'eTask'
LoginPrompt = False
Left = 255
Top = 379
Left = 267
Top = 395
end
object MySQLUniProvider1: TMySQLUniProvider
Left = 354
Top = 378
Left = 416
Top = 398
end
object uqUsers: TUniQuery
Connection = ucETaskApi
......@@ -236,17 +236,17 @@ object ApiDatabase: TApiDatabase
'values ('
' :TASK_ID,'
' :ITEM_NUM,'
' '#39#39','
' '#39#39','
' curdate(),'
' curdate(),'
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null'
')')
Left = 536
Top = 282
......@@ -580,7 +580,8 @@ object ApiDatabase: TApiDatabase
object uqProjectReportedUsers: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select distinct'
'select'
' min(tiu.TASK_ITEM_USER_ID) as TASK_ITEM_USER_ID,'
' tiu.NAME'
'from task_item_user tiu'
'join tasks project_tasks'
......@@ -589,6 +590,7 @@ object ApiDatabase: TApiDatabase
' on target_task.PROJECT_ID = project_tasks.PROJECT_ID'
'where target_task.TASK_ID = :TASK_ID'
' and tiu.USER_TYPE = '#39'Reported'#39
'group by tiu.NAME'
'order by tiu.NAME')
Left = 58
Top = 256
......@@ -598,6 +600,11 @@ object ApiDatabase: TApiDatabase
Name = 'TASK_ID'
Value = nil
end>
object uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField
FieldName = 'TASK_ITEM_USER_ID'
ReadOnly = True
Size = 50
end
object uqProjectReportedUsersNAME: TStringField
FieldName = 'NAME'
Required = True
......@@ -758,7 +765,7 @@ object ApiDatabase: TApiDatabase
Connection = ucETaskApi
SQL.Strings = (
'update task_items'
'set ASSIGNED_TO = '#39#39
'set ASSIGNED_TO = null'
'where TASK_ID = :TASK_ID'
' and lower(ASSIGNED_TO) = lower(:NAME)')
Left = 388
......@@ -775,4 +782,306 @@ object ApiDatabase: TApiDatabase
Value = nil
end>
end
object uqProjectApplications: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select distinct'
' tia.TASK_ITEM_APPLICATION_ID,'
' tia.PROJECT_ID,'
' tia.NAME'
'from task_item_application tia'
'join tasks target_task'
' on target_task.PROJECT_ID = tia.PROJECT_ID'
'where target_task.TASK_ID = :TASK_ID'
'order by tia.NAME')
Left = 58
Top = 314
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end>
object uqProjectApplicationsTASK_ITEM_APPLICATION_ID: TIntegerField
FieldName = 'TASK_ITEM_APPLICATION_ID'
end
object uqProjectApplicationsPROJECT_ID: TStringField
FieldName = 'PROJECT_ID'
Required = True
Size = 7
end
object uqProjectApplicationsNAME: TStringField
FieldName = 'NAME'
Required = True
Size = 255
end
end
object uqApplicationInsert: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'insert into task_item_application ('
' PROJECT_ID,'
' NAME'
')'
'values ('
' :PROJECT_ID,'
' :NAME'
')')
Left = 700
Top = 28
ParamData = <
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'NAME'
Value = nil
end>
end
object uqApplicationRename: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_item_application'
'set NAME = :NEW_NAME'
'where TASK_ITEM_APPLICATION_ID = :TASK_ITEM_APPLICATION_ID'
' and PROJECT_ID = :PROJECT_ID')
Left = 700
Top = 82
ParamData = <
item
DataType = ftUnknown
Name = 'NEW_NAME'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ITEM_APPLICATION_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end>
end
object uqApplicationDelete: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'delete from task_item_application'
'where TASK_ITEM_APPLICATION_ID = :TASK_ITEM_APPLICATION_ID'
' and PROJECT_ID = :PROJECT_ID')
Left = 700
Top = 136
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ITEM_APPLICATION_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end>
end
object uqRenameTaskItemApplication: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'set ti.APPLICATION = :NEW_NAME'
'where t.PROJECT_ID = :PROJECT_ID'
' and lower(ti.APPLICATION) = lower(:OLD_NAME)')
Left = 698
Top = 188
ParamData = <
item
DataType = ftUnknown
Name = 'NEW_NAME'
Value = nil
end
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'OLD_NAME'
Value = nil
end>
end
object uqBlankTaskItemApplication: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'set ti.APPLICATION = null'
'where t.PROJECT_ID = :PROJECT_ID'
' and lower(ti.APPLICATION) = lower(:NAME)')
Left = 696
Top = 244
ParamData = <
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'NAME'
Value = nil
end>
end
object uqReportedInsert: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'insert into task_item_user ('
' TASK_ITEM_USER_ID,'
' TASK_ID,'
' USER_TYPE,'
' NAME'
')'
'values ('
' :TASK_ITEM_USER_ID,'
' :TASK_ID,'
' '#39'Reported'#39','
' :NAME'
')')
Left = 858
Top = 32
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ITEM_USER_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'NAME'
Value = nil
end>
end
object uqReportedRename: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_item_user tiu'
'join tasks t'
' on t.TASK_ID = tiu.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'set tiu.NAME = :NEW_NAME'
'where target_task.TASK_ID = :TASK_ID'
' and tiu.USER_TYPE = '#39'Reported'#39
' and tiu.TASK_ITEM_USER_ID = :TASK_ITEM_USER_ID')
Left = 858
Top = 88
ParamData = <
item
DataType = ftUnknown
Name = 'NEW_NAME'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ITEM_USER_ID'
Value = nil
end>
end
object uqReportedDelete: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'delete tiu'
'from task_item_user tiu'
'join tasks t'
' on t.TASK_ID = tiu.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'where target_task.TASK_ID = :TASK_ID'
' and tiu.USER_TYPE = '#39'Reported'#39
' and tiu.TASK_ITEM_USER_ID = :TASK_ITEM_USER_ID')
Left = 858
Top = 144
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ITEM_USER_ID'
Value = nil
end>
end
object uqBlankTaskItemReportedBy: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'set ti.REPORTED_BY = null'
'where target_task.TASK_ID = :TASK_ID'
' and lower(ti.REPORTED_BY) = lower(:NAME)')
Left = 860
Top = 264
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'NAME'
Value = nil
end>
end
object uqRenameTaskItemReportedBy: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'set ti.REPORTED_BY = :NEW_NAME'
'where target_task.TASK_ID = :TASK_ID'
' and lower(ti.REPORTED_BY) = lower(:OLD_NAME)')
Left = 858
Top = 202
ParamData = <
item
DataType = ftUnknown
Name = 'NEW_NAME'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'OLD_NAME'
Value = nil
end>
end
end
......@@ -68,11 +68,26 @@ type
uqAssignedDelete: TUniQuery;
uqRenameAssignedTo: TUniQuery;
uqBlankAssignedTo: TUniQuery;
uqProjectReportedUsersNAME: TStringField;
uqTaskAssignedUsersTASK_ITEM_USER_ID: TStringField;
uqTaskAssignedUsersTASK_ID: TStringField;
uqTaskAssignedUsersUSER_TYPE: TStringField;
uqTaskAssignedUsersNAME: TStringField;
uqProjectApplications: TUniQuery;
uqApplicationInsert: TUniQuery;
uqApplicationRename: TUniQuery;
uqApplicationDelete: TUniQuery;
uqRenameTaskItemApplication: TUniQuery;
uqBlankTaskItemApplication: TUniQuery;
uqProjectApplicationsTASK_ITEM_APPLICATION_ID: TIntegerField;
uqProjectApplicationsPROJECT_ID: TStringField;
uqProjectApplicationsNAME: TStringField;
uqReportedInsert: TUniQuery;
uqReportedRename: TUniQuery;
uqReportedDelete: TUniQuery;
uqBlankTaskItemReportedBy: TUniQuery;
uqRenameTaskItemReportedBy: TUniQuery;
uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField;
uqProjectReportedUsersNAME: TStringField;
procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet);
private
......
......@@ -48,7 +48,11 @@ type
name: string;
end;
type
TTaskApplicationOption = class
public
name: string;
end;
TTaskItemCode = class
public
code: string;
......@@ -61,6 +65,7 @@ type
count: integer;
task: TTaskHeader;
items: TList<TTaskItem>;
applicationOptions: TList<TTaskApplicationOption>;
reportedByOptions: TList<TTaskUserOption>;
assignedToOptions: TList<TTaskUserOption>;
statusOptions: TList<TTaskItemCode>;
......@@ -71,6 +76,14 @@ type
TTaskUserOptionsResponse = class
public
assignedToOptions: TList<TTaskUserOption>;
reportedByOptions: TList<TTaskUserOption>;
constructor Create;
destructor Destroy; override;
end;
TTaskApplicationOptionsResponse = class
public
applicationOptions: TList<TTaskApplicationOption>;
constructor Create;
destructor Destroy; override;
end;
......@@ -93,21 +106,37 @@ type
notes: string;
end;
TTaskItemFieldSave = class
public
taskItemId: Integer;
fieldName: string;
value: string;
end;
type
[ServiceContract, Model(API_MODEL)]
IApiService = interface(IInvokable)
['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}']
function GetTaskItems(taskId: string): TTaskItemsResponse;
[HttpPost] function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
[HttpPost] function SaveTaskRow(Item: TTaskRowSave): Boolean;
[HttpPost] function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost] function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
[HttpPost] function DeleteAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost] function AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost]function RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
[HttpPost]function DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost] function AddApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
[HttpPost] function RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
[HttpPost] function DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
function DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
end;
implementation
......@@ -116,6 +145,7 @@ constructor TTaskItemsResponse.Create;
begin
inherited;
items := TList<TTaskItem>.Create;
applicationOptions := TList<TTaskApplicationOption>.Create;
reportedByOptions := TList<TTaskUserOption>.Create;
assignedToOptions := TList<TTaskUserOption>.Create;
statusOptions := TList<TTaskItemCode>.Create;
......@@ -124,6 +154,7 @@ end;
destructor TTaskItemsResponse.Destroy;
begin
items.Free;
applicationOptions.Free;
reportedByOptions.Free;
assignedToOptions.Free;
statusOptions.Free;
......@@ -134,11 +165,25 @@ constructor TTaskUserOptionsResponse.Create;
begin
inherited;
assignedToOptions := TList<TTaskUserOption>.Create;
reportedByOptions := TList<TTaskUserOption>.Create;
end;
destructor TTaskUserOptionsResponse.Destroy;
begin
assignedToOptions.Free;
assignedToOptions := TList<TTaskUserOption>.Create;
reportedByOptions := TList<TTaskUserOption>.Create;
inherited;
end;
constructor TTaskApplicationOptionsResponse.Create;
begin
inherited;
applicationOptions := TList<TTaskApplicationOption>.Create;
end;
destructor TTaskApplicationOptionsResponse.Destroy;
begin
applicationOptions.Free;
inherited;
end;
......
......@@ -3,14 +3,10 @@ unit Api.ServiceImpl;
interface
uses
XData.Server.Module,
XData.Service.Common,
System.Variants,
System.DateUtils,
Api.Service,
Api.Database,
Common.Logging,
System.SysUtils, JSON, System.IniFiles;
XData.Server.Module, XData.Service.Common,
System.Variants, System.DateUtils, Uni,
Api.Service, Api.Database, Common.Logging,
System.SysUtils, JSON, System.IniFiles, DBAccess;
type
[ServiceImplementation]
......@@ -23,13 +19,26 @@ type
function FindAssignedOptionId(const taskId, name: string): string;
function FindAssignedOptionName(const taskId, name: string): string;
function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
function SaveTaskRow(Item: TTaskRowSave): Boolean;
function BuildAssignedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
function DeleteAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
function DeleteTaskRow(const taskId, taskItemId: Integer): Boolean;
function FindProjectId(const taskId: string): string;
function FindApplicationId(const taskId, name: string): Integer;
function FindApplicationName(const taskId, name: string): string;
function BuildApplicationOptionsResponse(const taskId: string): TTaskApplicationOptionsResponse;
function AddApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
function RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
function DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
function FindReportedOptionId(const taskId, name: string): string;
function FindReportedOptionName(const taskId, name: string): string;
function BuildReportedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
function AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
function RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
function DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
......@@ -58,6 +67,7 @@ var
taskHeader: TTaskHeader;
taskUserOption: TTaskUserOption;
taskItemCode: TTaskItemCode;
taskApplicationOption: TTaskApplicationOption;
item: TTaskItem;
begin
Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId]));
......@@ -93,6 +103,21 @@ begin
Result.task := taskHeader;
apiDB.uqProjectApplications.Close;
apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectApplications.Open;
while not apiDB.uqProjectApplications.Eof do
begin
taskApplicationOption := TTaskApplicationOption.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskApplicationOption);
taskApplicationOption.name := apiDB.uqProjectApplicationsNAME.AsString;
Result.applicationOptions.Add(taskApplicationOption);
apiDB.uqProjectApplications.Next;
end;
apiDB.uqProjectReportedUsers.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open;
......@@ -343,6 +368,116 @@ begin
end;
function TApiService.SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
var
uqSaveField: TUniQuery;
columnName: string;
d: TDateTime;
function MapFieldNameToColumn(const AFieldName: string): string;
begin
Result := '';
if SameText(AFieldName, 'application') then
Result := 'APPLICATION'
else if SameText(AFieldName, 'version') then
Result := 'APP_VERSION'
else if SameText(AFieldName, 'taskDate') then
Result := 'TASK_DATE'
else if SameText(AFieldName, 'reportedBy') then
Result := 'REPORTED_BY'
else if SameText(AFieldName, 'assignedTo') then
Result := 'ASSIGNED_TO'
else if SameText(AFieldName, 'status') then
Result := 'STATUS'
else if SameText(AFieldName, 'statusDate') then
Result := 'STATUS_DATE'
else if SameText(AFieldName, 'formSection') then
Result := 'FORM_SECTION'
else if SameText(AFieldName, 'issue') then
Result := 'ISSUE'
else if SameText(AFieldName, 'notes') then
Result := 'NOTES'
else if SameText(AFieldName, 'fixedVersion') then
Result := 'FIXED_VERSION';
end;
function IsDateField(const AFieldName: string): Boolean;
begin
Result := SameText(AFieldName, 'taskDate') or SameText(AFieldName, 'statusDate');
end;
function ParseDateOrZero(const S: string; out ADate: TDateTime): Boolean;
var
y, m, dInt: Word;
begin
Result := False;
ADate := 0;
if Length(Trim(S)) <> 10 then
Exit;
y := StrToIntDef(Copy(S, 1, 4), 0);
m := StrToIntDef(Copy(S, 6, 2), 0);
dInt := StrToIntDef(Copy(S, 9, 2), 0);
if (y > 0) and (m > 0) and (dInt > 0) then
begin
try
ADate := EncodeDate(y, m, dInt);
Result := True;
except
Result := False;
end;
end;
end;
begin
Result := False;
if not Assigned(Item) then
raise Exception.Create('SaveTaskItemField: Item is nil.');
if Item.taskItemId <= 0 then
raise Exception.Create('SaveTaskItemField: Invalid taskItemId.');
columnName := MapFieldNameToColumn(Item.fieldName);
if columnName = '' then
raise Exception.Create('SaveTaskItemField: Invalid field name: ' + Item.fieldName);
uqSaveField := TUniQuery.Create(nil);
try
uqSaveField.Connection := apiDB.ucETaskApi;
uqSaveField.SQL.Text :=
'update task_items ' +
'set ' + columnName + ' = :' + columnName + ' ' +
'where TASK_ITEM_ID = :TASK_ITEM_ID';
uqSaveField.ParamByName('TASK_ITEM_ID').AsInteger := Item.taskItemId;
if IsDateField(Item.fieldName) then
begin
if ParseDateOrZero(Item.value, d) then
uqSaveField.ParamByName(columnName).AsDateTime := d
else
uqSaveField.ParamByName(columnName).Clear;
end
else
begin
if Trim(Item.value) = '' then
uqSaveField.ParamByName(columnName).Clear
else
uqSaveField.ParamByName(columnName).AsString := Item.value;
end;
uqSaveField.ExecSQL;
Result := True;
finally
uqSaveField.Free;
end;
end;
function TApiService.BuildTaskNumber: string;
procedure AddPart(const value: string);
......@@ -386,62 +521,6 @@ begin
end;
function TApiService.SaveTaskRow(Item: TTaskRowSave): Boolean;
function ParseDateOrZero(const S: string; out D: TDateTime): Boolean;
begin
Result := Trim(S) <> '';
if Result then
D := ISO8601ToDate(S, False);
end;
var
d: TDateTime;
begin
Logger.Log(4, Format('ApiService.SaveTaskRow - TASK_ITEM_ID="%d"', [Item.taskItemId]));
try
apiDB.uqSaveTaskRow.Close;
apiDB.uqSaveTaskRow.ParamByName('TASK_ITEM_ID').AsInteger := Item.taskItemId;
apiDB.uqSaveTaskRow.ParamByName('APPLICATION').AsString := Item.application;
apiDB.uqSaveTaskRow.ParamByName('APP_VERSION').AsString := Item.version;
if ParseDateOrZero(Item.taskDate, d) then
apiDB.uqSaveTaskRow.ParamByName('TASK_DATE').AsDateTime := d
else
apiDB.uqSaveTaskRow.ParamByName('TASK_DATE').Clear;
apiDB.uqSaveTaskRow.ParamByName('REPORTED_BY').AsString := Item.reportedBy;
apiDB.uqSaveTaskRow.ParamByName('ASSIGNED_TO').AsString := Item.assignedTo;
apiDB.uqSaveTaskRow.ParamByName('STATUS').AsString := Item.status;
if ParseDateOrZero(Item.statusDate, d) then
apiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := d
else
apiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := Date;
apiDB.uqSaveTaskRow.ParamByName('FIXED_VERSION').AsString := Item.fixedVersion;
apiDB.uqSaveTaskRow.ParamByName('FORM_SECTION').AsString := Item.formSection;
apiDB.uqSaveTaskRow.ParamByName('ISSUE').AsString := Item.issue;
apiDB.uqSaveTaskRow.ParamByName('NOTES').AsString := Item.notes;
apiDB.uqSaveTaskRow.ExecSQL;
Result := True;
Logger.Log(4, 'ApiService.SaveTaskRow - OK');
except
on E: Exception do
begin
Logger.Log(2, 'ApiService.SaveTaskRow - ERROR: ' + E.Message);
raise;
end;
end;
end;
function TApiService.DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
var
oldItemNum: Integer;
......@@ -687,6 +766,427 @@ begin
end;
end;
function TApiService.FindProjectId(const taskId: string): string;
begin
Result := '';
apiDB.uqTaskHeader.Close;
apiDB.uqTaskHeader.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqTaskHeader.Open;
if not apiDB.uqTaskHeader.IsEmpty then
Result := apiDB.uqTaskHeaderPROJECT_ID.AsString;
end;
function TApiService.FindApplicationId(const taskId, name: string): Integer;
begin
Result := 0;
apiDB.uqProjectApplications.Close;
apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectApplications.Open;
while not apiDB.uqProjectApplications.Eof do
begin
if SameText(Trim(apiDB.uqProjectApplicationsNAME.AsString), Trim(name)) then
begin
Result := apiDB.uqProjectApplicationsTASK_ITEM_APPLICATION_ID.AsInteger;
Exit;
end;
apiDB.uqProjectApplications.Next;
end;
end;
function TApiService.FindApplicationName(const taskId, name: string): string;
begin
Result := '';
apiDB.uqProjectApplications.Close;
apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectApplications.Open;
while not apiDB.uqProjectApplications.Eof do
begin
if SameText(Trim(apiDB.uqProjectApplicationsNAME.AsString), Trim(name)) then
begin
Result := apiDB.uqProjectApplicationsNAME.AsString;
Exit;
end;
apiDB.uqProjectApplications.Next;
end;
end;
function TApiService.BuildApplicationOptionsResponse(const taskId: string): TTaskApplicationOptionsResponse;
var
taskApplicationOption: TTaskApplicationOption;
begin
Result := TTaskApplicationOptionsResponse.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
apiDB.uqProjectApplications.Close;
apiDB.uqProjectApplications.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectApplications.Open;
while not apiDB.uqProjectApplications.Eof do
begin
taskApplicationOption := TTaskApplicationOption.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskApplicationOption);
taskApplicationOption.name := apiDB.uqProjectApplicationsNAME.AsString;
Result.applicationOptions.Add(taskApplicationOption);
apiDB.uqProjectApplications.Next;
end;
end;
function TApiService.AddApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
var
projectId: string;
newName: string;
existingName: string;
begin
newName := Trim(name);
if newName = '' then
raise Exception.Create('Application name cannot be blank.');
projectId := FindProjectId(taskId);
if projectId = '' then
raise Exception.Create('Project not found for task.');
existingName := FindApplicationName(taskId, newName);
if existingName = '' then
begin
apiDB.uqApplicationInsert.Connection.StartTransaction;
try
apiDB.uqApplicationInsert.Close;
apiDB.uqApplicationInsert.ParamByName('PROJECT_ID').AsString := projectId;
apiDB.uqApplicationInsert.ParamByName('NAME').AsString := newName;
apiDB.uqApplicationInsert.ExecSQL;
apiDB.uqApplicationInsert.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqApplicationInsert.Connection.InTransaction then
apiDB.uqApplicationInsert.Connection.Rollback;
raise;
end;
end;
end;
Result := BuildApplicationOptionsResponse(taskId);
end;
function TApiService.RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
var
projectId: string;
oldApplicationId: Integer;
existingApplicationId: Integer;
trimmedOldName: string;
trimmedNewName: string;
begin
trimmedOldName := Trim(oldName);
trimmedNewName := Trim(newName);
if trimmedOldName = '' then
raise Exception.Create('Old application name cannot be blank.');
if trimmedNewName = '' then
raise Exception.Create('New application name cannot be blank.');
projectId := FindProjectId(taskId);
if projectId = '' then
raise Exception.Create('Project not found for task.');
oldApplicationId := FindApplicationId(taskId, trimmedOldName);
if oldApplicationId = 0 then
raise Exception.Create('Application name not found.');
existingApplicationId := FindApplicationId(taskId, trimmedNewName);
apiDB.uqApplicationRename.Connection.StartTransaction;
try
apiDB.uqRenameTaskItemApplication.Close;
apiDB.uqRenameTaskItemApplication.ParamByName('PROJECT_ID').AsString := projectId;
apiDB.uqRenameTaskItemApplication.ParamByName('OLD_NAME').AsString := trimmedOldName;
apiDB.uqRenameTaskItemApplication.ParamByName('NEW_NAME').AsString := trimmedNewName;
apiDB.uqRenameTaskItemApplication.ExecSQL;
if (existingApplicationId <> 0) and (existingApplicationId <> oldApplicationId) then
begin
apiDB.uqApplicationDelete.Close;
apiDB.uqApplicationDelete.ParamByName('TASK_ITEM_APPLICATION_ID').AsInteger := oldApplicationId;
apiDB.uqApplicationDelete.ParamByName('PROJECT_ID').AsString := projectId;
apiDB.uqApplicationDelete.ExecSQL;
end
else
begin
apiDB.uqApplicationRename.Close;
apiDB.uqApplicationRename.ParamByName('TASK_ITEM_APPLICATION_ID').AsInteger := oldApplicationId;
apiDB.uqApplicationRename.ParamByName('PROJECT_ID').AsString := projectId;
apiDB.uqApplicationRename.ParamByName('NEW_NAME').AsString := trimmedNewName;
apiDB.uqApplicationRename.ExecSQL;
end;
apiDB.uqApplicationRename.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqApplicationRename.Connection.InTransaction then
apiDB.uqApplicationRename.Connection.Rollback;
raise;
end;
end;
Result := BuildApplicationOptionsResponse(taskId);
end;
function TApiService.DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
var
projectId: string;
applicationId: Integer;
applicationName: string;
begin
projectId := FindProjectId(taskId);
if projectId = '' then
raise Exception.Create('Project not found for task.');
applicationId := FindApplicationId(taskId, name);
applicationName := FindApplicationName(taskId, name);
if applicationId = 0 then
raise Exception.Create('Application name not found.');
apiDB.uqApplicationDelete.Connection.StartTransaction;
try
apiDB.uqBlankTaskItemApplication.Close;
apiDB.uqBlankTaskItemApplication.ParamByName('PROJECT_ID').AsString := projectId;
apiDB.uqBlankTaskItemApplication.ParamByName('NAME').AsString := applicationName;
apiDB.uqBlankTaskItemApplication.ExecSQL;
apiDB.uqApplicationDelete.Close;
apiDB.uqApplicationDelete.ParamByName('TASK_ITEM_APPLICATION_ID').AsInteger := applicationId;
apiDB.uqApplicationDelete.ParamByName('PROJECT_ID').AsString := projectId;
apiDB.uqApplicationDelete.ExecSQL;
apiDB.uqApplicationDelete.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqApplicationDelete.Connection.InTransaction then
apiDB.uqApplicationDelete.Connection.Rollback;
raise;
end;
end;
Result := BuildApplicationOptionsResponse(taskId);
end;
function TApiService.FindReportedOptionId(const taskId, name: string): string;
begin
Result := '';
apiDB.uqProjectReportedUsers.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open;
while not apiDB.uqProjectReportedUsers.Eof do
begin
if SameText(Trim(apiDB.uqProjectReportedUsersNAME.AsString), Trim(name)) then
begin
Result := apiDB.uqProjectReportedUsersTASK_ITEM_USER_ID.AsString;
Exit;
end;
apiDB.uqProjectReportedUsers.Next;
end;
end;
function TApiService.FindReportedOptionName(const taskId, name: string): string;
begin
Result := '';
apiDB.uqProjectReportedUsers.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open;
while not apiDB.uqProjectReportedUsers.Eof do
begin
if SameText(Trim(apiDB.uqProjectReportedUsersNAME.AsString), Trim(name)) then
begin
Result := apiDB.uqProjectReportedUsersNAME.AsString;
Exit;
end;
apiDB.uqProjectReportedUsers.Next;
end;
end;
function TApiService.BuildReportedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
var
taskUserOption: TTaskUserOption;
begin
Result := TTaskUserOptionsResponse.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
apiDB.uqProjectReportedUsers.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open;
while not apiDB.uqProjectReportedUsers.Eof do
begin
taskUserOption := TTaskUserOption.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskUserOption);
taskUserOption.name := apiDB.uqProjectReportedUsersNAME.AsString;
Result.reportedByOptions.Add(taskUserOption);
apiDB.uqProjectReportedUsers.Next;
end;
end;
function TApiService.AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
newName: string;
existingName: string;
newGuid: TGuid;
begin
newName := Trim(name);
if newName = '' then
raise Exception.Create('Reported name cannot be blank.');
existingName := FindReportedOptionName(taskId, newName);
if existingName = '' then
begin
CreateGUID(newGuid);
apiDB.uqReportedInsert.Connection.StartTransaction;
try
apiDB.uqReportedInsert.Close;
apiDB.uqReportedInsert.ParamByName('TASK_ITEM_USER_ID').AsString := GuidToString(newGuid);
apiDB.uqReportedInsert.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedInsert.ParamByName('NAME').AsString := newName;
apiDB.uqReportedInsert.ExecSQL;
apiDB.uqReportedInsert.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqReportedInsert.Connection.InTransaction then
apiDB.uqReportedInsert.Connection.Rollback;
raise;
end;
end;
end;
Result := BuildReportedOptionsResponse(taskId);
end;
function TApiService.RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
var
oldReportedId: string;
existingReportedId: string;
trimmedOldName: string;
trimmedNewName: string;
begin
trimmedOldName := Trim(oldName);
trimmedNewName := Trim(newName);
if trimmedOldName = '' then
raise Exception.Create('Old reported name cannot be blank.');
if trimmedNewName = '' then
raise Exception.Create('New reported name cannot be blank.');
oldReportedId := FindReportedOptionId(taskId, trimmedOldName);
if oldReportedId = '' then
raise Exception.Create('Reported name not found.');
existingReportedId := FindReportedOptionId(taskId, trimmedNewName);
apiDB.uqReportedRename.Connection.StartTransaction;
try
apiDB.uqRenameTaskItemReportedBy.Close;
apiDB.uqRenameTaskItemReportedBy.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqRenameTaskItemReportedBy.ParamByName('OLD_NAME').AsString := trimmedOldName;
apiDB.uqRenameTaskItemReportedBy.ParamByName('NEW_NAME').AsString := trimmedNewName;
apiDB.uqRenameTaskItemReportedBy.ExecSQL;
if (existingReportedId <> '') and (not SameText(existingReportedId, oldReportedId)) then
begin
apiDB.uqReportedDelete.Close;
apiDB.uqReportedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := oldReportedId;
apiDB.uqReportedDelete.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedDelete.ExecSQL;
end
else
begin
apiDB.uqReportedRename.Close;
apiDB.uqReportedRename.ParamByName('TASK_ITEM_USER_ID').AsString := oldReportedId;
apiDB.uqReportedRename.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedRename.ParamByName('NEW_NAME').AsString := trimmedNewName;
apiDB.uqReportedRename.ExecSQL;
end;
apiDB.uqReportedRename.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqReportedRename.Connection.InTransaction then
apiDB.uqReportedRename.Connection.Rollback;
raise;
end;
end;
Result := BuildReportedOptionsResponse(taskId);
end;
function TApiService.DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
reportedId: string;
reportedName: string;
begin
reportedId := FindReportedOptionId(taskId, name);
reportedName := FindReportedOptionName(taskId, name);
if reportedId = '' then
raise Exception.Create('Reported name not found.');
apiDB.uqReportedDelete.Connection.StartTransaction;
try
apiDB.uqBlankTaskItemReportedBy.Close;
apiDB.uqBlankTaskItemReportedBy.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqBlankTaskItemReportedBy.ParamByName('NAME').AsString := reportedName;
apiDB.uqBlankTaskItemReportedBy.ExecSQL;
apiDB.uqReportedDelete.Close;
apiDB.uqReportedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := reportedId;
apiDB.uqReportedDelete.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedDelete.ExecSQL;
apiDB.uqReportedDelete.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqReportedDelete.Connection.InTransaction then
apiDB.uqReportedDelete.Connection.Rollback;
raise;
end;
end;
Result := BuildReportedOptionsResponse(taskId);
end;
initialization
RegisterServiceType(TypeInfo(IApiService));
RegisterServiceType(TApiService);
......
......@@ -190,8 +190,8 @@ begin
if userState = 0 then
begin
Logger.Log(2, 'Login Error: Invalid code');
raise EXDataHttpUnauthorized.Create('Invalid code');
Logger.Log(2, 'Login Error: Invalid url parameters');
raise EXDataHttpUnauthorized.Create('Invalid url parameters');
end;
if userState = 1 then
......
[Settings]
MemoLogLevel=4
FileLogLevel=4
webClientVersion=0.8.6
LogFileNum=152
webClientVersion=0.8.8
LogFileNum=161
[Database]
--Server=192.168.116.131
--Server=192.168.116.128
Server=192.168.102.131
--Server=192.168.116.128
--Server=192.168.102.131
--Server=192.168.159.10
Database=eTask
Username=root
......
......@@ -114,10 +114,10 @@
<VerInfo_Locale>1033</VerInfo_Locale>
<DCC_ExeOutput>.\bin</DCC_ExeOutput>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<VerInfo_Keys>CompanyName=EM Systems;FileDescription=$(MSBuildProjectName);FileVersion=0.8.6.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.11;Comments=</VerInfo_Keys>
<VerInfo_Keys>CompanyName=EM Systems;FileDescription=$(MSBuildProjectName);FileVersion=0.8.8.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.11;Comments=</VerInfo_Keys>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>8</VerInfo_MinorVer>
<VerInfo_Release>6</VerInfo_Release>
<VerInfo_Release>8</VerInfo_Release>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
......
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