Commit 9ca47a7c by Mac Stephens

Merge branch 'master' into dev

parents 8a2f5e16 72262daa
emT3VCLDemo/__history/ emT3VCLDemo/__history/
emT3VCLDemo/__recovery/
emT3VCLDemo/Win64x/ emT3VCLDemo/Win64x/
emT3Web/__history/ emT3Web/__history/
emT3Web/__recovery/ emT3Web/__recovery/
...@@ -19,4 +20,5 @@ emT3XDataServer/Win32/ ...@@ -19,4 +20,5 @@ emT3XDataServer/Win32/
*.txt *.txt
*.zip *.zip
emT3VCLDemo/__recovery/
emT3Web/css/__history/
...@@ -20,7 +20,7 @@ type ...@@ -20,7 +20,7 @@ type
FUnauthorizedAccessProc: TUnauthorizedAccessProc; FUnauthorizedAccessProc: TUnauthorizedAccessProc;
public public
const clientVersion = '0.8.6'; const clientVersion = '0.8.8';
procedure InitApp(SuccessProc: TSuccessProc; procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc); UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback); procedure SetClientConfig(Callback: TVersionCheckCallback);
......
...@@ -14,6 +14,7 @@ procedure ShowToast(const MessageText: string; const ToastType: string = 'succes ...@@ -14,6 +14,7 @@ procedure ShowToast(const MessageText: string; const ToastType: string = 'succes
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>); procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
procedure ShowNotificationModal(msg: string); procedure ShowNotificationModal(msg: string);
procedure ShowAppDialog(const Msg: string; const Title: string = 'emT3 web app'; ReloadOnClose: Boolean = False; ButtonText: string = 'Close'); procedure ShowAppDialog(const Msg: string; const Title: string = 'emT3 web app'; ReloadOnClose: Boolean = False; ButtonText: string = 'Close');
function NormalizeDateValue(const Value: string): string;
implementation implementation
...@@ -281,5 +282,22 @@ begin ...@@ -281,5 +282,22 @@ begin
end; 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. end.
...@@ -44,6 +44,20 @@ object FTaskItems: TFTaskItems ...@@ -44,6 +44,20 @@ object FTaskItems: TFTaskItems
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
OnClick = btnDeleteRowClick OnClick = btnDeleteRowClick
end 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 object xdwcTasks: TXDataWebClient
Connection = DMConnection.ApiConnection Connection = DMConnection.ApiConnection
Left = 506 Left = 506
......
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<div id="lbl_total_rows"></div> <div id="lbl_total_rows"></div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button id="btn_add_row" class="btn btn-sm btn-success">Add Row</button> <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_delete_row" class="btn btn-sm btn-danger">Delete Row</button>
<button id="btn_reload" class="btn btn-sm btn-primary">Reload</button> <button id="btn_insert_row" class="btn btn-sm btn-success">Insert Row</button>
</div> <button id="btn_reload" class="btn btn-sm btn-primary">Reload</button>
</div>
</div> </div>
</div> </div>
......
...@@ -5,7 +5,7 @@ interface ...@@ -5,7 +5,7 @@ interface
uses uses
System.SysUtils, System.Classes, System.SysUtils, System.Classes,
JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs, JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
WEBLib.ExtCtrls, uNameManager, WEBLib.ExtCtrls, uNameOffCanvas, uDropdownHelpers,
XData.Web.Client, XData.Web.Dataset, XData.Web.Client, XData.Web.Dataset,
Utils, Data.DB, XData.Web.JsonDataset, Vcl.Controls, Vcl.StdCtrls, Utils, Data.DB, XData.Web.JsonDataset, Vcl.Controls, Vcl.StdCtrls,
WEBLib.StdCtrls; WEBLib.StdCtrls;
...@@ -30,12 +30,15 @@ type ...@@ -30,12 +30,15 @@ type
xdwdsTasksitemNum: TIntegerField; xdwdsTasksitemNum: TIntegerField;
xdwdsTaskstaskItemId: TIntegerField; xdwdsTaskstaskItemId: TIntegerField;
btnDeleteRow: TWebButton; btnDeleteRow: TWebButton;
btnInsertRow: TWebButton;
[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);
[async] procedure btnDeleteRowClick(Sender: TObject); [async] procedure btnDeleteRowClick(Sender: TObject);
[async] procedure btnInsertRowClick(Sender: TObject);
private private
FTaskId: string; FTaskId: string;
FApplicationOptions: TJSArray;
FReportedByOptions: TJSArray; FReportedByOptions: TJSArray;
FAssignedToOptions: TJSArray; FAssignedToOptions: TJSArray;
FStatusOptions: TJSArray; FStatusOptions: TJSArray;
...@@ -59,16 +62,14 @@ type ...@@ -59,16 +62,14 @@ type
function HtmlEncode(const s: string): string; function HtmlEncode(const s: string): string;
procedure SetTotalRowsLabel(ARowCount: Integer); procedure SetTotalRowsLabel(ARowCount: Integer);
procedure SetTaskLabel(const ATitle: string); procedure SetTaskLabel(const ATitle: string);
[async] procedure SaveRow(AIndex: Integer); [async] procedure SaveField(AIndex: Integer; const AFieldName: string);
procedure EditorBlur(Event: TJSEvent); 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; [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 DropdownItemClick(Event: TJSEvent);
procedure DropdownEditClick(Event: TJSEvent); procedure DropdownEditClick(Event: TJSEvent);
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
[async] procedure MoveTaskRow(AIndex: Integer; const newItemNum: Integer); [async] procedure MoveTaskRow(AIndex: Integer; const newItemNum: Integer);
procedure ApplyPendingFocus; procedure ApplyPendingFocus;
procedure RowClick(Event: TJSEvent); procedure RowClick(Event: TJSEvent);
...@@ -80,11 +81,15 @@ type ...@@ -80,11 +81,15 @@ type
[async] function AddAssignedName(const AName: string): TJSArray; [async] function AddAssignedName(const AName: string): TJSArray;
[async] function RenameAssignedName(const AOldName, ANewName: string): TJSArray; [async] function RenameAssignedName(const AOldName, ANewName: string): TJSArray;
[async] function DeleteAssignedName(const AName: string): TJSArray; [async] function DeleteAssignedName(const AName: string): TJSArray;
function ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray; [async] function AddReportedName(const AName: string): TJSArray;
[async] procedure HandleAddAssignedName(const ARowIndex: Integer; const ANewName: string); [async] function RenameReportedName(const AOldName, ANewName: string): TJSArray;
[async] procedure HandleRenameAssignedName(const AOldName, ANewName: string); [async] function DeleteReportedName(const AName: string): TJSArray;
[async] procedure HandleDeleteAssignedName(const AName: string); [async] function AddApplicationName(const AName: string): TJSArray;
function FindAssignedOptionIgnoreCase(const AItems: TJSArray; const AName: string): string; [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 public
end; end;
...@@ -103,6 +108,7 @@ procedure TFTaskItems.WebFormCreate(Sender: TObject); ...@@ -103,6 +108,7 @@ procedure TFTaskItems.WebFormCreate(Sender: TObject);
begin begin
console.log('TFTaskItems.WebFormCreate fired'); console.log('TFTaskItems.WebFormCreate fired');
FTaskId := Application.Parameters.Values['task_id']; FTaskId := Application.Parameters.Values['task_id'];
FApplicationOptions := TJSArray.new;
FReportedByOptions := TJSArray.new; FReportedByOptions := TJSArray.new;
FAssignedToOptions := TJSArray.new; FAssignedToOptions := TJSArray.new;
FStatusOptions := TJSArray.new; FStatusOptions := TJSArray.new;
...@@ -112,23 +118,23 @@ begin ...@@ -112,23 +118,23 @@ begin
FNameManager := TNameManager.Create( FNameManager := TNameManager.Create(
function(const AFieldName: string): TJSArray function(const AFieldName: string): TJSArray
begin begin
Result := GetOptionsForField(AFieldName); Result := uDropdownHelpers.GetOptionsForField(AFieldName, FApplicationOptions, FReportedByOptions, FAssignedToOptions);
end, end,
procedure(const ATriggerId: string) procedure(const ATriggerId: string)
begin begin
FocusTrigger(ATriggerId); uDropdownHelpers.FocusTrigger(ATriggerId);
end, end,
procedure(const ARowIndex: Integer; const ANewName: string) procedure(const AFieldName: string; const ARowIndex: Integer; const ANewName: string)
begin begin
HandleAddAssignedName(ARowIndex, ANewName); HandleAddManagedName(AFieldName, ARowIndex, ANewName);
end, end,
procedure(const AOldName, ANewName: string) procedure(const AFieldName, AOldName, ANewName: string)
begin begin
HandleRenameAssignedName(AOldName, ANewName); HandleRenameManagedName(AFieldName, AOldName, ANewName);
end, end,
procedure(const AName: string) procedure(const AFieldName, AName: string)
begin begin
HandleDeleteAssignedName(AName); HandleDeleteManagedName(AFieldName, AName);
end end
); );
...@@ -140,6 +146,7 @@ begin ...@@ -140,6 +146,7 @@ begin
Exit; Exit;
end; end;
btnInsertRow.Enabled := False;
btnAddRow.Enabled := False; btnAddRow.Enabled := False;
btnDeleteRow.Enabled := False; btnDeleteRow.Enabled := False;
...@@ -253,7 +260,7 @@ begin ...@@ -253,7 +260,7 @@ begin
xdwdsTasks.Post; xdwdsTasks.Post;
el.setAttribute('data-unsaved-data', '1'); el.setAttribute('data-unsaved-data', '1');
SaveRow(idx); SaveField(idx, fieldName);
end; end;
...@@ -342,7 +349,7 @@ end; ...@@ -342,7 +349,7 @@ end;
begin begin
Utils.ShowSpinner('spinner'); Utils.ShowSpinner('spinner');
try try
if await(AddTaskRow) then if await(AddTaskRowAtBottom) then
begin begin
CaptureTableScroll; CaptureTableScroll;
LoadTasks(FTaskId); LoadTasks(FTaskId);
...@@ -381,6 +388,7 @@ begin ...@@ -381,6 +388,7 @@ begin
begin begin
FSelectedTaskItemId := 0; FSelectedTaskItemId := 0;
FSelectedTaskId := 0; FSelectedTaskId := 0;
btnInsertRow.Enabled := False;
btnDeleteRow.Enabled := False; btnDeleteRow.Enabled := False;
FPendingFocusItemNum := deletedItemNum; FPendingFocusItemNum := deletedItemNum;
...@@ -394,6 +402,20 @@ begin ...@@ -394,6 +402,20 @@ begin
end; end;
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); procedure TFTaskItems.RowClick(Event: TJSEvent);
var var
rowEl: TJSHTMLElement; rowEl: TJSHTMLElement;
...@@ -410,6 +432,7 @@ begin ...@@ -410,6 +432,7 @@ begin
FSelectedTaskItemId := StrToIntDef(taskItemIdStr, 0); FSelectedTaskItemId := StrToIntDef(taskItemIdStr, 0);
FSelectedTaskId := StrToIntDef(taskIdStr, 0); FSelectedTaskId := StrToIntDef(taskIdStr, 0);
btnInsertRow.Enabled := FSelectedTaskItemId > 0;
btnDeleteRow.Enabled := FSelectedTaskItemId > 0; btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
ApplySelectedRowState; ApplySelectedRowState;
end; end;
...@@ -429,43 +452,76 @@ begin ...@@ -429,43 +452,76 @@ begin
end; end;
[async] function TFTaskItems.AddTaskRow: Boolean; [async] function TFTaskItems.AddTaskRowAtBottom: Boolean;
var var
response: TXDataClientResponse;
insertAfterItemNum: Integer;
newItemNum: Integer;
maxItemNum: Integer; maxItemNum: Integer;
begin 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; Result := False;
if FTaskId = '' then if FSelectedTaskItemId <= 0 then
begin begin
Utils.ShowErrorModal('Missing task_id. Please reopen from emt3.'); Utils.ShowErrorModal('Select a row first.');
Exit; Exit;
end; end;
insertAfterItemNum := 0; insertAfterItemNum := 0;
maxItemNum := 0;
if xdwdsTasks.Active then if xdwdsTasks.Active then
begin begin
xdwdsTasks.First; xdwdsTasks.First;
while not xdwdsTasks.Eof do while not xdwdsTasks.Eof do
begin begin
if xdwdsTasksitemNum.AsInteger > maxItemNum then
maxItemNum := xdwdsTasksitemNum.AsInteger;
if xdwdsTaskstaskItemId.AsInteger = FSelectedTaskItemId then if xdwdsTaskstaskItemId.AsInteger = FSelectedTaskItemId then
begin
insertAfterItemNum := xdwdsTasksitemNum.AsInteger; insertAfterItemNum := xdwdsTasksitemNum.AsInteger;
Break;
end;
xdwdsTasks.Next; xdwdsTasks.Next;
end; end;
end; end;
if insertAfterItemNum > 0 then if insertAfterItemNum <= 0 then
newItemNum := insertAfterItemNum + 1 begin
else Utils.ShowErrorModal('Unable to find selected row.');
newItemNum := maxItemNum + 1; 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 try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
...@@ -575,6 +631,7 @@ begin ...@@ -575,6 +631,7 @@ begin
rowCount := StrToIntDef(string(resultObj['count']), 0); rowCount := StrToIntDef(string(resultObj['count']), 0);
SetTotalRowsLabel(rowCount); SetTotalRowsLabel(rowCount);
FApplicationOptions := ExtractOptionNames(TJSArray(resultObj['applicationOptions']));
FReportedByOptions := ExtractOptionNames(TJSArray(resultObj['reportedByOptions'])); FReportedByOptions := ExtractOptionNames(TJSArray(resultObj['reportedByOptions']));
FAssignedToOptions := ExtractOptionNames(TJSArray(resultObj['assignedToOptions'])); FAssignedToOptions := ExtractOptionNames(TJSArray(resultObj['assignedToOptions']));
FStatusOptions := ExtractCodeDescs(TJSArray(resultObj['statusOptions'])); FStatusOptions := ExtractCodeDescs(TJSArray(resultObj['statusOptions']));
...@@ -588,6 +645,7 @@ begin ...@@ -588,6 +645,7 @@ begin
xdwdsTasks.Open; xdwdsTasks.Open;
btnAddRow.Enabled := True; btnAddRow.Enabled := True;
btnInsertRow.Enabled := FSelectedTaskItemId > 0;
btnDeleteRow.Enabled := FSelectedTaskItemId > 0; btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
RenderTable; RenderTable;
...@@ -623,13 +681,11 @@ var ...@@ -623,13 +681,11 @@ var
Result := '<td class="align-top wrap-cell">' + s + '</td>'; Result := '<td class="align-top wrap-cell">' + s + '</td>';
end; 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 var
w: string; w: string;
begin begin
w := ''; w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"';
Result := Result :=
'<input class="form-control form-control-sm cell-input task-editor w-100" ' + '<input class="form-control form-control-sm cell-input task-editor w-100" ' +
...@@ -655,18 +711,19 @@ var ...@@ -655,18 +711,19 @@ var
'value="' + IntToStr(Value) + '">'; '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): string;
var var
w: string; w: string;
dateValue: string;
begin begin
w := ''; w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"'; dateValue := Utils.NormalizeDateValue(Value);
Result := Result :=
'<input type="date" class="form-control form-control-sm cell-input task-editor w-100" ' + '<input type="date" class="form-control form-control-sm cell-input task-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' + 'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'value="' + HtmlEncode(Value) + '"' + w + '>'; 'value="' + HtmlEncode(dateValue) + '"' + w + '>';
end; end;
function SelectList(const FieldName, Current: string; const AIdx: Integer; const Items: TJSArray; const AllowEdit: Boolean): string; function SelectList(const FieldName, Current: string; const AIdx: Integer; const Items: TJSArray; const AllowEdit: Boolean): string;
...@@ -674,13 +731,21 @@ var ...@@ -674,13 +731,21 @@ var
i: Integer; i: Integer;
itemText: string; itemText: string;
triggerId: string; triggerId: string;
buttonClass: string;
begin begin
triggerId := 'task_dd_' + FieldName + '_' + IntToStr(AIdx); 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 := Result :=
'<div class="dropdown w-100">' + '<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" ' + '<button id="' + triggerId + '" class="' + buttonClass + '" ' +
'type="button" data-bs-toggle="dropdown" aria-expanded="false">' + '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="task-dd-label text-truncate">' + HtmlEncode(Current) + '</span>' +
'<span class="dropdown-toggle dropdown-toggle-split border-0 ms-2"></span>' + '<span class="dropdown-toggle dropdown-toggle-split border-0 ms-2"></span>' +
'</button>' + '</button>' +
...@@ -721,31 +786,6 @@ var ...@@ -721,31 +786,6 @@ var
'</div>'; '</div>';
end; 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 begin
host := TJSHTMLElement(document.getElementById('tasks_table_host')); host := TJSHTMLElement(document.getElementById('tasks_table_host'));
if not Assigned(host) then if not Assigned(host) then
...@@ -754,12 +794,12 @@ begin ...@@ -754,12 +794,12 @@ 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 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>' + '<colgroup>' +
'<col style="width:40px">' + // Item Num '<col style="width:40px">' + // Item Num
'<col style="width:200px">' + // App '<col style="width:200px">' + // App
'<col style="width:90px">' + // Version '<col style="width:90px">' + // Version
'<col style="width:120px">' + // Date '<col style="width:140px">' + // Date
'<col style="width:120px">' + // Reported '<col style="width:120px">' + // Reported
'<col style="width:120px">' + // Assigned '<col style="width:120px">' + // Assigned
'<col style="width:195px">' + // Status '<col style="width:195px">' + // Status
...@@ -778,7 +818,7 @@ begin ...@@ -778,7 +818,7 @@ begin
Th('Status') + Th('Status') +
Th('Status Date') + Th('Status Date') +
Th('Form') + Th('Form') +
Th('Issue') + Th('Issue/To-Do') +
Th('Notes') + Th('Notes') +
'</tr></thead><tbody>'; '</tr></thead><tbody>';
...@@ -789,14 +829,14 @@ begin ...@@ -789,14 +829,14 @@ begin
html := html + 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) + '">' + '<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(ItemNumInput(xdwdsTasksitemNum.AsInteger, rowIdx)) +
TdNowrap(TextInput('application', xdwdsTasksapplication.AsString, rowIdx, 180)) + TdNowrap(SelectList('application', xdwdsTasksapplication.AsString, rowIdx, FApplicationOptions, True)) +
TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx, 80)) + TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx)) +
TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx, 110)) + TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx)) +
TdNowrap(SelectList('reportedBy', xdwdsTasksreportedBy.AsString, rowIdx, FReportedByOptions, False)) + TdNowrap(SelectList('reportedBy', xdwdsTasksreportedBy.AsString, rowIdx, FReportedByOptions, True)) +
TdNowrap(SelectList('assignedTo', xdwdsTasksassignedTo.AsString, rowIdx, FAssignedToOptions, True)) + TdNowrap(SelectList('assignedTo', xdwdsTasksassignedTo.AsString, rowIdx, FAssignedToOptions, True)) +
TdNowrap(StatusSelect(xdwdsTasksstatus.AsString, rowIdx)) + TdNowrap(SelectList('status', xdwdsTasksstatus.AsString, rowIdx, FStatusOptions, False)) +
TdNowrap(DateInput('statusDate', xdwdsTasksstatusDate.AsString, rowIdx, 110)) + TdNowrap(DateInput('statusDate', xdwdsTasksstatusDate.AsString, rowIdx)) +
TdNowrap(TextInput('formSection', xdwdsTasksformSection.AsString, rowIdx, 160)) + TdNowrap(TextInput('formSection', xdwdsTasksformSection.AsString, rowIdx)) +
TdWrap(TextArea('issue', xdwdsTasksissue.AsString, rowIdx)) + TdWrap(TextArea('issue', xdwdsTasksissue.AsString, rowIdx)) +
TdWrap(TextArea('notes', xdwdsTasksnotes.AsString, rowIdx)) + TdWrap(TextArea('notes', xdwdsTasksnotes.AsString, rowIdx)) +
'</tr>'; '</tr>';
...@@ -823,12 +863,16 @@ begin ...@@ -823,12 +863,16 @@ begin
(function(){ (function(){
const host = document.getElementById('tasks_table_host'); const host = document.getElementById('tasks_table_host');
if(!host) return; if(!host) return;
const table = host.querySelector('table'); const table = host.querySelector('table');
if(!table) return; if(!table) return;
const ths = table.querySelectorAll('thead th'); const ths = table.querySelectorAll('thead th');
ths.forEach(th => { const cols = table.querySelectorAll('colgroup col');
ths.forEach((th, index) => {
th.classList.add('th-resize'); th.classList.add('th-resize');
if(th.querySelector('.th-resize-handle')) return; if (th.querySelector('.th-resize-handle')) return;
const handle = document.createElement('div'); const handle = document.createElement('div');
handle.className = 'th-resize-handle'; handle.className = 'th-resize-handle';
...@@ -836,17 +880,27 @@ begin ...@@ -836,17 +880,27 @@ begin
handle.addEventListener('mousedown', function(e){ handle.addEventListener('mousedown', function(e){
e.preventDefault(); e.preventDefault();
e.stopPropagation();
const startX = e.clientX; const startX = e.clientX;
const startW = th.getBoundingClientRect().width; const startW = th.getBoundingClientRect().width;
function onMove(ev){ 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.width = w + 'px';
th.style.minWidth = '0';
if (cols && cols[index]) {
cols[index].style.width = w + 'px';
}
} }
function onUp(){ function onUp(){
document.removeEventListener('mousemove', onMove); document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp); document.removeEventListener('mouseup', onUp);
} }
document.addEventListener('mousemove', onMove); document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp); document.addEventListener('mouseup', onUp);
}); });
...@@ -889,7 +943,7 @@ begin ...@@ -889,7 +943,7 @@ begin
end; end;
console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName); console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName);
SaveRow(idx); SaveField(idx, fieldName);
end; end;
...@@ -934,14 +988,13 @@ begin ...@@ -934,14 +988,13 @@ begin
end; end;
[async] procedure TFTaskItems.SaveRow(AIndex: Integer); [async] procedure TFTaskItems.SaveField(AIndex: Integer; const AFieldName: string);
const const
// Note: Use this to manipulate saving to the server or not for testing
ENABLE_SERVER_SAVE = True; ENABLE_SERVER_SAVE = True;
var var
response: TXDataClientResponse; response: TXDataClientResponse;
payload: TJSObject; payload: TJSObject;
payloadJson: string; fieldValue: string;
begin begin
if not xdwdsTasks.Active then if not xdwdsTasks.Active then
Exit; Exit;
...@@ -950,35 +1003,47 @@ begin ...@@ -950,35 +1003,47 @@ begin
if xdwdsTasks.Eof then if xdwdsTasks.Eof then
Exit; 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['taskItemId'] := xdwdsTaskstaskItemId.AsInteger;
payload['taskId'] := xdwdsTaskstaskId.AsString; payload['fieldName'] := AFieldName;
payload['value'] := fieldValue;
payload['application'] := xdwdsTasksapplication.AsString;
payload['version'] := xdwdsTasksversion.AsString; console.log('SaveField: idx=' + IntToStr(AIndex) + ' field=' + AFieldName + ' value=' + fieldValue);
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);
if not ENABLE_SERVER_SAVE then if not ENABLE_SERVER_SAVE then
Exit; Exit;
try try
response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskRow', [payload])); response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskItemField', [payload]));
console.log('SaveRow: response=' + string(TJSJSON.stringify(response.Result))); console.log('SaveField: response=' + string(TJSJSON.stringify(response.Result)));
except except
on E: EXDataClientRequestException do on E: EXDataClientRequestException do
begin begin
console.log('SaveRow ERROR: ' + E.ErrorResult.ErrorMessage); console.log('SaveField ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage); Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end; end;
end; end;
...@@ -1010,73 +1075,14 @@ begin ...@@ -1010,73 +1075,14 @@ begin
end; end;
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); procedure TFTaskItems.DropdownItemClick(Event: TJSEvent);
var var
el: TJSHTMLElement; el: TJSHTMLElement;
idx: Integer; idx: Integer;
idxStr, fieldName, newVal, triggerId: string; idxStr, fieldName, newVal, triggerId: string;
btn: TJSHTMLElement;
labelEl: TJSHTMLElement;
begin begin
if not xdwdsTasks.Active then if not xdwdsTasks.Active then
Exit; Exit;
...@@ -1104,18 +1110,19 @@ begin ...@@ -1104,18 +1110,19 @@ begin
if triggerId <> '' then if triggerId <> '' then
begin begin
asm btn := TJSHTMLElement(document.getElementById(triggerId));
var btn = document.getElementById(triggerId); if Assigned(btn) then
if (btn) { begin
var labelEl = btn.querySelector('.task-dd-label'); labelEl := TJSHTMLElement(btn.querySelector('.task-dd-label'));
if (labelEl) { if Assigned(labelEl) then
labelEl.textContent = newVal; labelEl.textContent := newVal;
}
} if SameText(fieldName, 'status') then
ApplyTaskStatusClass(btn, newVal);
end; end;
end; end;
SaveRow(idx); SaveField(idx, fieldName);
end; end;
...@@ -1138,7 +1145,7 @@ begin ...@@ -1138,7 +1145,7 @@ begin
if (idx < 0) or (fieldName = '') then if (idx < 0) or (fieldName = '') then
Exit; Exit;
if not SameText(fieldName, 'assignedTo') then if not (SameText(fieldName, 'assignedTo') or SameText(fieldName, 'application') or SameText(fieldName, 'reportedBy')) then
Exit; Exit;
FNameManager.OpenManager(fieldName, idx, triggerId); FNameManager.OpenManager(fieldName, idx, triggerId);
...@@ -1198,12 +1205,16 @@ begin ...@@ -1198,12 +1205,16 @@ begin
FSelectedTaskItemId := StrToIntDef(taskItemIdStr, 0); FSelectedTaskItemId := StrToIntDef(taskItemIdStr, 0);
FSelectedTaskId := StrToIntDef(taskIdStr, 0); FSelectedTaskId := StrToIntDef(taskIdStr, 0);
btnInsertRow.Enabled := FSelectedTaskItemId > 0;
btnDeleteRow.Enabled := FSelectedTaskItemId > 0; btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
ApplySelectedRowState; ApplySelectedRowState;
asm
rowEl.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' });
end;
end; end;
asm asm
el.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' });
el.focus(); el.focus();
end; end;
end; end;
...@@ -1235,14 +1246,6 @@ begin ...@@ -1235,14 +1246,6 @@ begin
end; 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; [async] function TFTaskItems.AddAssignedName(const AName: string): TJSArray;
var var
response: TXDataClientResponse; response: TXDataClientResponse;
...@@ -1321,36 +1324,32 @@ begin ...@@ -1321,36 +1324,32 @@ begin
end; end;
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 var
newOptions: TJSArray; newOptions: TJSArray;
resolvedName: string; resolvedName: string;
begin 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 if not Assigned(newOptions) then
Exit; Exit;
FAssignedToOptions := newOptions; if SameText(AFieldName, 'application') then
resolvedName := FindAssignedOptionIgnoreCase(FAssignedToOptions, ANewName); FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
resolvedName := FindOptionIgnoreCase(newOptions, ANewName);
if resolvedName = '' then if resolvedName = '' then
resolvedName := Trim(ANewName); resolvedName := Trim(ANewName);
...@@ -1362,39 +1361,224 @@ begin ...@@ -1362,39 +1361,224 @@ begin
Exit; Exit;
xdwdsTasks.Edit; xdwdsTasks.Edit;
xdwdsTasksassignedTo.AsString := resolvedName; xdwdsTasks.FieldByName(AFieldName).AsString := resolvedName;
xdwdsTasks.Post; xdwdsTasks.Post;
RenderTable; RenderTable;
await(SaveRow(ARowIndex)); await(SaveField(ARowIndex, AFieldName));
end; end;
[async] procedure TFTaskItems.HandleRenameAssignedName(const AOldName, ANewName: string); [async] procedure TFTaskItems.HandleRenameManagedName(const AFieldName, AOldName, ANewName: string);
var var
newOptions: TJSArray; newOptions: TJSArray;
begin 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 if not Assigned(newOptions) then
Exit; Exit;
FAssignedToOptions := newOptions; if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
CaptureTableScroll; CaptureTableScroll;
LoadTasks(FTaskId); LoadTasks(FTaskId);
end; end;
[async] procedure TFTaskItems.HandleDeleteAssignedName(const AName: string); [async] procedure TFTaskItems.HandleDeleteManagedName(const AFieldName, AName: string);
var var
newOptions: TJSArray; newOptions: TJSArray;
begin 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 if not Assigned(newOptions) then
Exit; Exit;
FAssignedToOptions := newOptions; if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
CaptureTableScroll; CaptureTableScroll;
LoadTasks(FTaskId); LoadTasks(FTaskId);
end; 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"] { ...@@ -62,6 +62,9 @@ input[data-field="itemNum"] {
top: 0; top: 0;
z-index: 2; z-index: 2;
background: var(--bs-body-bg); background: var(--bs-body-bg);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.tasks-vscroll thead th.th-resize { .tasks-vscroll thead th.th-resize {
...@@ -86,5 +89,155 @@ span.card { ...@@ -86,5 +89,155 @@ span.card {
user-select: none; 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 ...@@ -12,7 +12,8 @@ uses
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html}, View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
Utils in 'Utils.pas', Utils in 'Utils.pas',
View.TaskItems in 'View.TaskItems.pas' {FTaskItems: TWebForm} {*.html}, View.TaskItems in 'View.TaskItems.pas' {FTaskItems: TWebForm} {*.html},
uNameManager in 'uNameManager.pas'; uNameOffCanvas in 'uNameOffCanvas.pas',
uDropdownHelpers in 'uDropdownHelpers.pas';
{$R *.res} {$R *.res}
......
...@@ -94,13 +94,13 @@ ...@@ -94,13 +94,13 @@
<DCC_RemoteDebug>false</DCC_RemoteDebug> <DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale> <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> <AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<VerInfo_MajorVer>0</VerInfo_MajorVer> <VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>8</VerInfo_MinorVer> <VerInfo_MinorVer>8</VerInfo_MinorVer>
<VerInfo_Release>6</VerInfo_Release> <VerInfo_Release>8</VerInfo_Release>
<TMSWebBrowser>5</TMSWebBrowser>
<TMSUseJSDebugger>2</TMSUseJSDebugger> <TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebSingleInstance>1</TMSWebSingleInstance> <TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath> <TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
</PropertyGroup> </PropertyGroup>
...@@ -142,7 +142,8 @@ ...@@ -142,7 +142,8 @@
<FormType>dfm</FormType> <FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="uNameManager.pas"/> <DCCReference Include="uNameOffCanvas.pas"/>
<DCCReference Include="uDropdownHelpers.pas"/>
<None Include="index.html"/> <None Include="index.html"/>
<None Include="css\app.css"/> <None Include="css\app.css"/>
<None Include="config\config.json"/> <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 interface
...@@ -8,9 +8,9 @@ uses ...@@ -8,9 +8,9 @@ uses
type type
TGetOptionsEvent = reference to function(const AFieldName: string): TJSArray; TGetOptionsEvent = reference to function(const AFieldName: string): TJSArray;
TFocusTriggerEvent = reference to procedure(const ATriggerId: string); TFocusTriggerEvent = reference to procedure(const ATriggerId: string);
TAddAssignedNameEvent = reference to procedure(const ARowIndex: Integer; const ANewName: string); TAddManagedNameEvent = reference to procedure(const AFieldName: string; const ARowIndex: Integer; const ANewName: string);
TRenameAssignedNameEvent = reference to procedure(const AOldName, ANewName: string); TRenameManagedNameEvent = reference to procedure(const AFieldName, AOldName, ANewName: string);
TDeleteAssignedNameEvent = reference to procedure(const AName: string); TDeleteManagedNameEvent = reference to procedure(const AFieldName, AName: string);
TNameManager = class TNameManager = class
private private
...@@ -20,9 +20,9 @@ type ...@@ -20,9 +20,9 @@ type
FCurrentEditName: string; FCurrentEditName: string;
FGetOptions: TGetOptionsEvent; FGetOptions: TGetOptionsEvent;
FFocusTrigger: TFocusTriggerEvent; FFocusTrigger: TFocusTriggerEvent;
FAddAssignedName: TAddAssignedNameEvent; FAddManagedName: TAddManagedNameEvent;
FRenameAssignedName: TRenameAssignedNameEvent; FRenameManagedName: TRenameManagedNameEvent;
FDeleteAssignedName: TDeleteAssignedNameEvent; FDeleteManagedName: TDeleteManagedNameEvent;
procedure AddAnotherClick(Event: TJSEvent); procedure AddAnotherClick(Event: TJSEvent);
procedure SaveClick(Event: TJSEvent); procedure SaveClick(Event: TJSEvent);
procedure NameInputKeyDown(Event: TJSEvent); procedure NameInputKeyDown(Event: TJSEvent);
...@@ -40,9 +40,9 @@ type ...@@ -40,9 +40,9 @@ type
constructor Create( constructor Create(
const AGetOptions: TGetOptionsEvent; const AGetOptions: TGetOptionsEvent;
const AFocusTrigger: TFocusTriggerEvent; const AFocusTrigger: TFocusTriggerEvent;
const AAddAssignedName: TAddAssignedNameEvent; const AAddManagedName: TAddManagedNameEvent;
const ARenameAssignedName: TRenameAssignedNameEvent; const ARenameManagedName: TRenameManagedNameEvent;
const ADeleteAssignedName: TDeleteAssignedNameEvent const ADeleteManagedName: TDeleteManagedNameEvent
); );
procedure BindControls; procedure BindControls;
...@@ -70,17 +70,17 @@ end; ...@@ -70,17 +70,17 @@ end;
constructor TNameManager.Create( constructor TNameManager.Create(
const AGetOptions: TGetOptionsEvent; const AGetOptions: TGetOptionsEvent;
const AFocusTrigger: TFocusTriggerEvent; const AFocusTrigger: TFocusTriggerEvent;
const AAddAssignedName: TAddAssignedNameEvent; const AAddManagedName: TAddManagedNameEvent;
const ARenameAssignedName: TRenameAssignedNameEvent; const ARenameManagedName: TRenameManagedNameEvent;
const ADeleteAssignedName: TDeleteAssignedNameEvent const ADeleteManagedName: TDeleteManagedNameEvent
); );
begin begin
inherited Create; inherited Create;
FGetOptions := AGetOptions; FGetOptions := AGetOptions;
FFocusTrigger := AFocusTrigger; FFocusTrigger := AFocusTrigger;
FAddAssignedName := AAddAssignedName; FAddManagedName := AAddManagedName;
FRenameAssignedName := ARenameAssignedName; FRenameManagedName := ARenameManagedName;
FDeleteAssignedName := ADeleteAssignedName; FDeleteManagedName := ADeleteManagedName;
FCurrentField := ''; FCurrentField := '';
FCurrentRowIndex := -1; FCurrentRowIndex := -1;
FCurrentTriggerId := ''; FCurrentTriggerId := '';
...@@ -257,18 +257,18 @@ begin ...@@ -257,18 +257,18 @@ begin
Exit; Exit;
end; end;
if not SameText(FCurrentField, 'assignedTo') then if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit; Exit;
if FCurrentEditName <> '' then if FCurrentEditName <> '' then
begin begin
if Assigned(FRenameAssignedName) then if Assigned(FRenameManagedName) then
FRenameAssignedName(FCurrentEditName, newName); FRenameManagedName(FCurrentField, FCurrentEditName, newName);
end end
else else
begin begin
if Assigned(FAddAssignedName) then if Assigned(FAddManagedName) then
FAddAssignedName(FCurrentRowIndex, newName); FAddManagedName(FCurrentField, FCurrentRowIndex, newName);
end; end;
CloseManager; CloseManager;
...@@ -356,7 +356,7 @@ begin ...@@ -356,7 +356,7 @@ begin
Event.preventDefault; Event.preventDefault;
Event.stopPropagation; Event.stopPropagation;
if not SameText(FCurrentField, 'assignedTo') then if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit; Exit;
el := TJSHTMLElement(Event.currentTarget); el := TJSHTMLElement(Event.currentTarget);
...@@ -383,7 +383,7 @@ begin ...@@ -383,7 +383,7 @@ begin
Event.preventDefault; Event.preventDefault;
Event.stopPropagation; Event.stopPropagation;
if not SameText(FCurrentField, 'assignedTo') then if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit; Exit;
el := TJSHTMLElement(Event.currentTarget); el := TJSHTMLElement(Event.currentTarget);
...@@ -391,8 +391,8 @@ begin ...@@ -391,8 +391,8 @@ begin
if nameToDelete = '' then if nameToDelete = '' then
Exit; Exit;
if Assigned(FDeleteAssignedName) then if Assigned(FDeleteManagedName) then
FDeleteAssignedName(nameToDelete); FDeleteManagedName(FCurrentField, nameToDelete);
CloseManager; CloseManager;
end; end;
...@@ -401,7 +401,7 @@ procedure TNameManager.OpenManager(const AFieldName: string; const ARowIndex: In ...@@ -401,7 +401,7 @@ procedure TNameManager.OpenManager(const AFieldName: string; const ARowIndex: In
var var
titleEl: TJSHTMLElement; titleEl: TJSHTMLElement;
begin begin
if not SameText(AFieldName, 'assignedTo') then if not (SameText(AFieldName, 'assignedTo') or SameText(AFieldName, 'application') or SameText(AFieldName, 'reportedBy')) then
Exit; Exit;
FCurrentField := AFieldName; FCurrentField := AFieldName;
...@@ -411,7 +411,14 @@ begin ...@@ -411,7 +411,14 @@ begin
titleEl := GetElement('nm_title'); titleEl := GetElement('nm_title');
if Assigned(titleEl) then if Assigned(titleEl) then
titleEl.innerHTML := 'Manage Assigned To'; 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; ResetInputArea;
RenderExistingList; RenderExistingList;
......
object ApiDatabase: TApiDatabase object ApiDatabase: TApiDatabase
OnCreate = DataModuleCreate OnCreate = DataModuleCreate
Height = 453 Height = 475
Width = 641 Width = 996
object ucETaskApi: TUniConnection object ucETaskApi: TUniConnection
AutoCommit = False AutoCommit = False
ProviderName = 'MySQL' ProviderName = 'MySQL'
Database = 'eTask' Database = 'eTask'
LoginPrompt = False LoginPrompt = False
Left = 255 Left = 267
Top = 379 Top = 395
end end
object MySQLUniProvider1: TMySQLUniProvider object MySQLUniProvider1: TMySQLUniProvider
Left = 354 Left = 416
Top = 378 Top = 398
end end
object uqUsers: TUniQuery object uqUsers: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
...@@ -236,17 +236,17 @@ object ApiDatabase: TApiDatabase ...@@ -236,17 +236,17 @@ object ApiDatabase: TApiDatabase
'values (' 'values ('
' :TASK_ID,' ' :TASK_ID,'
' :ITEM_NUM,' ' :ITEM_NUM,'
' '#39#39',' ' null,'
' '#39#39',' ' null,'
' curdate(),' ' null,'
' curdate(),' ' null,'
' '#39#39',' ' null,'
' '#39#39',' ' null,'
' '#39#39',' ' null,'
' '#39#39',' ' null,'
' '#39#39',' ' null,'
' '#39#39',' ' null,'
' '#39#39 ' null'
')') ')')
Left = 536 Left = 536
Top = 282 Top = 282
...@@ -580,7 +580,8 @@ object ApiDatabase: TApiDatabase ...@@ -580,7 +580,8 @@ object ApiDatabase: TApiDatabase
object uqProjectReportedUsers: TUniQuery object uqProjectReportedUsers: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
'select distinct' 'select'
' min(tiu.TASK_ITEM_USER_ID) as TASK_ITEM_USER_ID,'
' tiu.NAME' ' tiu.NAME'
'from task_item_user tiu' 'from task_item_user tiu'
'join tasks project_tasks' 'join tasks project_tasks'
...@@ -589,6 +590,7 @@ object ApiDatabase: TApiDatabase ...@@ -589,6 +590,7 @@ object ApiDatabase: TApiDatabase
' on target_task.PROJECT_ID = project_tasks.PROJECT_ID' ' on target_task.PROJECT_ID = project_tasks.PROJECT_ID'
'where target_task.TASK_ID = :TASK_ID' 'where target_task.TASK_ID = :TASK_ID'
' and tiu.USER_TYPE = '#39'Reported'#39 ' and tiu.USER_TYPE = '#39'Reported'#39
'group by tiu.NAME'
'order by tiu.NAME') 'order by tiu.NAME')
Left = 58 Left = 58
Top = 256 Top = 256
...@@ -598,6 +600,11 @@ object ApiDatabase: TApiDatabase ...@@ -598,6 +600,11 @@ object ApiDatabase: TApiDatabase
Name = 'TASK_ID' Name = 'TASK_ID'
Value = nil Value = nil
end> end>
object uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField
FieldName = 'TASK_ITEM_USER_ID'
ReadOnly = True
Size = 50
end
object uqProjectReportedUsersNAME: TStringField object uqProjectReportedUsersNAME: TStringField
FieldName = 'NAME' FieldName = 'NAME'
Required = True Required = True
...@@ -758,7 +765,7 @@ object ApiDatabase: TApiDatabase ...@@ -758,7 +765,7 @@ object ApiDatabase: TApiDatabase
Connection = ucETaskApi Connection = ucETaskApi
SQL.Strings = ( SQL.Strings = (
'update task_items' 'update task_items'
'set ASSIGNED_TO = '#39#39 'set ASSIGNED_TO = null'
'where TASK_ID = :TASK_ID' 'where TASK_ID = :TASK_ID'
' and lower(ASSIGNED_TO) = lower(:NAME)') ' and lower(ASSIGNED_TO) = lower(:NAME)')
Left = 388 Left = 388
...@@ -775,4 +782,306 @@ object ApiDatabase: TApiDatabase ...@@ -775,4 +782,306 @@ object ApiDatabase: TApiDatabase
Value = nil Value = nil
end> end>
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 end
...@@ -68,11 +68,26 @@ type ...@@ -68,11 +68,26 @@ type
uqAssignedDelete: TUniQuery; uqAssignedDelete: TUniQuery;
uqRenameAssignedTo: TUniQuery; uqRenameAssignedTo: TUniQuery;
uqBlankAssignedTo: TUniQuery; uqBlankAssignedTo: TUniQuery;
uqProjectReportedUsersNAME: TStringField;
uqTaskAssignedUsersTASK_ITEM_USER_ID: TStringField; uqTaskAssignedUsersTASK_ITEM_USER_ID: TStringField;
uqTaskAssignedUsersTASK_ID: TStringField; uqTaskAssignedUsersTASK_ID: TStringField;
uqTaskAssignedUsersUSER_TYPE: TStringField; uqTaskAssignedUsersUSER_TYPE: TStringField;
uqTaskAssignedUsersNAME: 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 DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet); procedure uqUsersCalcFields(DataSet: TDataSet);
private private
......
...@@ -48,7 +48,11 @@ type ...@@ -48,7 +48,11 @@ type
name: string; name: string;
end; end;
type TTaskApplicationOption = class
public
name: string;
end;
TTaskItemCode = class TTaskItemCode = class
public public
code: string; code: string;
...@@ -61,6 +65,7 @@ type ...@@ -61,6 +65,7 @@ type
count: integer; count: integer;
task: TTaskHeader; task: TTaskHeader;
items: TList<TTaskItem>; items: TList<TTaskItem>;
applicationOptions: TList<TTaskApplicationOption>;
reportedByOptions: TList<TTaskUserOption>; reportedByOptions: TList<TTaskUserOption>;
assignedToOptions: TList<TTaskUserOption>; assignedToOptions: TList<TTaskUserOption>;
statusOptions: TList<TTaskItemCode>; statusOptions: TList<TTaskItemCode>;
...@@ -71,6 +76,14 @@ type ...@@ -71,6 +76,14 @@ type
TTaskUserOptionsResponse = class TTaskUserOptionsResponse = class
public public
assignedToOptions: TList<TTaskUserOption>; assignedToOptions: TList<TTaskUserOption>;
reportedByOptions: TList<TTaskUserOption>;
constructor Create;
destructor Destroy; override;
end;
TTaskApplicationOptionsResponse = class
public
applicationOptions: TList<TTaskApplicationOption>;
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
end; end;
...@@ -93,21 +106,37 @@ type ...@@ -93,21 +106,37 @@ type
notes: string; notes: string;
end; end;
TTaskItemFieldSave = class
public
taskItemId: Integer;
fieldName: string;
value: string;
end;
type type
[ServiceContract, Model(API_MODEL)] [ServiceContract, Model(API_MODEL)]
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; insertAfterItemNum: Integer): Boolean; [HttpPost] function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
[HttpPost] function SaveTaskRow(Item: TTaskRowSave): Boolean;
[HttpPost] function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse; [HttpPost] function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost] function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse; [HttpPost] function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
[HttpPost] function DeleteAssignedName(taskId: string; name: 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); procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
function DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean; function DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
end; end;
implementation implementation
...@@ -116,6 +145,7 @@ constructor TTaskItemsResponse.Create; ...@@ -116,6 +145,7 @@ constructor TTaskItemsResponse.Create;
begin begin
inherited; inherited;
items := TList<TTaskItem>.Create; items := TList<TTaskItem>.Create;
applicationOptions := TList<TTaskApplicationOption>.Create;
reportedByOptions := TList<TTaskUserOption>.Create; reportedByOptions := TList<TTaskUserOption>.Create;
assignedToOptions := TList<TTaskUserOption>.Create; assignedToOptions := TList<TTaskUserOption>.Create;
statusOptions := TList<TTaskItemCode>.Create; statusOptions := TList<TTaskItemCode>.Create;
...@@ -124,6 +154,7 @@ end; ...@@ -124,6 +154,7 @@ end;
destructor TTaskItemsResponse.Destroy; destructor TTaskItemsResponse.Destroy;
begin begin
items.Free; items.Free;
applicationOptions.Free;
reportedByOptions.Free; reportedByOptions.Free;
assignedToOptions.Free; assignedToOptions.Free;
statusOptions.Free; statusOptions.Free;
...@@ -134,11 +165,25 @@ constructor TTaskUserOptionsResponse.Create; ...@@ -134,11 +165,25 @@ constructor TTaskUserOptionsResponse.Create;
begin begin
inherited; inherited;
assignedToOptions := TList<TTaskUserOption>.Create; assignedToOptions := TList<TTaskUserOption>.Create;
reportedByOptions := TList<TTaskUserOption>.Create;
end; end;
destructor TTaskUserOptionsResponse.Destroy; destructor TTaskUserOptionsResponse.Destroy;
begin 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; inherited;
end; end;
......
...@@ -3,14 +3,10 @@ unit Api.ServiceImpl; ...@@ -3,14 +3,10 @@ unit Api.ServiceImpl;
interface interface
uses uses
XData.Server.Module, XData.Server.Module, XData.Service.Common,
XData.Service.Common, System.Variants, System.DateUtils, Uni,
System.Variants, Api.Service, Api.Database, Common.Logging,
System.DateUtils, System.SysUtils, JSON, System.IniFiles, DBAccess;
Api.Service,
Api.Database,
Common.Logging,
System.SysUtils, JSON, System.IniFiles;
type type
[ServiceImplementation] [ServiceImplementation]
...@@ -23,13 +19,26 @@ type ...@@ -23,13 +19,26 @@ type
function FindAssignedOptionId(const taskId, name: string): string; function FindAssignedOptionId(const taskId, name: string): string;
function FindAssignedOptionName(const taskId, name: string): string; function FindAssignedOptionName(const taskId, name: string): string;
function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean; function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
function SaveTaskRow(Item: TTaskRowSave): Boolean;
function BuildAssignedOptionsResponse(const taskId: string): TTaskUserOptionsResponse; function BuildAssignedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse; function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse; function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
function DeleteAssignedName(taskId: string; name: string): TTaskUserOptionsResponse; function DeleteAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer); procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
function DeleteTaskRow(const taskId, taskItemId: Integer): Boolean; 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 public
procedure AfterConstruction; override; procedure AfterConstruction; override;
procedure BeforeDestruction; override; procedure BeforeDestruction; override;
...@@ -58,6 +67,7 @@ var ...@@ -58,6 +67,7 @@ var
taskHeader: TTaskHeader; taskHeader: TTaskHeader;
taskUserOption: TTaskUserOption; taskUserOption: TTaskUserOption;
taskItemCode: TTaskItemCode; taskItemCode: TTaskItemCode;
taskApplicationOption: TTaskApplicationOption;
item: TTaskItem; item: TTaskItem;
begin begin
Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId])); Logger.Log(4, Format('ApiService.GetTaskItems - TASK_ID="%s"', [taskId]));
...@@ -93,6 +103,21 @@ begin ...@@ -93,6 +103,21 @@ begin
Result.task := taskHeader; 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.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId; apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open; apiDB.uqProjectReportedUsers.Open;
...@@ -343,6 +368,116 @@ begin ...@@ -343,6 +368,116 @@ begin
end; 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; function TApiService.BuildTaskNumber: string;
procedure AddPart(const value: string); procedure AddPart(const value: string);
...@@ -386,62 +521,6 @@ begin ...@@ -386,62 +521,6 @@ begin
end; 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; function TApiService.DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
var var
oldItemNum: Integer; oldItemNum: Integer;
...@@ -687,6 +766,427 @@ begin ...@@ -687,6 +766,427 @@ begin
end; end;
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 initialization
RegisterServiceType(TypeInfo(IApiService)); RegisterServiceType(TypeInfo(IApiService));
RegisterServiceType(TApiService); RegisterServiceType(TApiService);
......
...@@ -190,8 +190,8 @@ begin ...@@ -190,8 +190,8 @@ begin
if userState = 0 then if userState = 0 then
begin begin
Logger.Log(2, 'Login Error: Invalid code'); Logger.Log(2, 'Login Error: Invalid url parameters');
raise EXDataHttpUnauthorized.Create('Invalid code'); raise EXDataHttpUnauthorized.Create('Invalid url parameters');
end; end;
if userState = 1 then if userState = 1 then
......
[Settings] [Settings]
MemoLogLevel=4 MemoLogLevel=4
FileLogLevel=4 FileLogLevel=4
webClientVersion=0.8.6 webClientVersion=0.8.8
LogFileNum=152 LogFileNum=161
[Database] [Database]
--Server=192.168.116.131
--Server=192.168.116.128
Server=192.168.102.131 Server=192.168.102.131
--Server=192.168.116.128
--Server=192.168.102.131
--Server=192.168.159.10 --Server=192.168.159.10
Database=eTask Database=eTask
Username=root Username=root
......
...@@ -114,10 +114,10 @@ ...@@ -114,10 +114,10 @@
<VerInfo_Locale>1033</VerInfo_Locale> <VerInfo_Locale>1033</VerInfo_Locale>
<DCC_ExeOutput>.\bin</DCC_ExeOutput> <DCC_ExeOutput>.\bin</DCC_ExeOutput>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> <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_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>8</VerInfo_MinorVer> <VerInfo_MinorVer>8</VerInfo_MinorVer>
<VerInfo_Release>6</VerInfo_Release> <VerInfo_Release>8</VerInfo_Release>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''"> <PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode> <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