Commit 4a314fa8 by Mac Stephens

v0.8.5 fixes Deployed 0.8.8

parent 071af034
......@@ -20,7 +20,7 @@ type
FUnauthorizedAccessProc: TUnauthorizedAccessProc;
public
const clientVersion = '0.8.7';
const clientVersion = '0.8.8';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback);
......
......@@ -14,6 +14,7 @@ procedure ShowToast(const MessageText: string; const ToastType: string = 'succes
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
procedure ShowNotificationModal(msg: string);
procedure ShowAppDialog(const Msg: string; const Title: string = 'emT3 web app'; ReloadOnClose: Boolean = False; ButtonText: string = 'Close');
function NormalizeDateValue(const Value: string): string;
implementation
......@@ -281,5 +282,22 @@ begin
end;
function NormalizeDateValue(const Value: string): string;
var
normalizedValue: string;
begin
normalizedValue := Trim(Value);
if (normalizedValue = '') or
SameText(normalizedValue, '1899-12-30') or
SameText(normalizedValue, '12/30/1899') or
SameText(normalizedValue, '1899-12-30T00:00:00') or
SameText(normalizedValue, '1899-12-30 00:00:00') then
Result := ''
else
Result := normalizedValue;
end;
end.
......@@ -5,7 +5,7 @@ interface
uses
System.SysUtils, System.Classes,
JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
WEBLib.ExtCtrls, uNameManager,
WEBLib.ExtCtrls, uNameOffCanvas, uDropdownHelpers,
XData.Web.Client, XData.Web.Dataset,
Utils, Data.DB, XData.Web.JsonDataset, Vcl.Controls, Vcl.StdCtrls,
WEBLib.StdCtrls;
......@@ -60,16 +60,12 @@ type
function HtmlEncode(const s: string): string;
procedure SetTotalRowsLabel(ARowCount: Integer);
procedure SetTaskLabel(const ATitle: string);
[async] procedure SaveRow(AIndex: Integer);
[async] procedure SaveField(AIndex: Integer; const AFieldName: string);
procedure EditorBlur(Event: TJSEvent);
[async] function AddTaskRow: Boolean;
[async] function DeleteTaskRow: Boolean;
function ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
function GetOptionsForField(const AFieldName: string): TJSArray;
procedure FocusTrigger(const ATriggerId: string);
procedure DropdownItemClick(Event: TJSEvent);
procedure DropdownEditClick(Event: TJSEvent);
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
[async] procedure MoveTaskRow(AIndex: Integer; const newItemNum: Integer);
procedure ApplyPendingFocus;
procedure RowClick(Event: TJSEvent);
......@@ -81,15 +77,15 @@ type
[async] function AddAssignedName(const AName: string): TJSArray;
[async] function RenameAssignedName(const AOldName, ANewName: string): TJSArray;
[async] function DeleteAssignedName(const AName: string): TJSArray;
function ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
[async] function AddReportedName(const AName: string): TJSArray;
[async] function RenameReportedName(const AOldName, ANewName: string): TJSArray;
[async] function DeleteReportedName(const AName: string): TJSArray;
[async] function AddApplicationName(const AName: string): TJSArray;
[async] function RenameApplicationName(const AOldName, ANewName: string): TJSArray;
[async] function DeleteApplicationName(const AName: string): TJSArray;
function ExtractApplicationOptionNames(const ResponseResult: TJSObject): 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);
function FindOptionIgnoreCase(const AItems: TJSArray; const AName: string): string;
public
end;
......@@ -118,11 +114,11 @@ begin
FNameManager := TNameManager.Create(
function(const AFieldName: string): TJSArray
begin
Result := GetOptionsForField(AFieldName);
Result := uDropdownHelpers.GetOptionsForField(AFieldName, FApplicationOptions, FReportedByOptions, FAssignedToOptions);
end,
procedure(const ATriggerId: string)
begin
FocusTrigger(ATriggerId);
uDropdownHelpers.FocusTrigger(ATriggerId);
end,
procedure(const AFieldName: string; const ARowIndex: Integer; const ANewName: string)
begin
......@@ -259,7 +255,7 @@ begin
xdwdsTasks.Post;
el.setAttribute('data-unsaved-data', '1');
SaveRow(idx);
SaveField(idx, fieldName);
end;
......@@ -630,13 +626,11 @@ var
Result := '<td class="align-top wrap-cell">' + s + '</td>';
end;
function TextInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string;
function TextInput(const FieldName, Value: string; const AIdx: Integer): string;
var
w: string;
begin
w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"';
Result :=
'<input class="form-control form-control-sm cell-input task-editor w-100" ' +
......@@ -662,18 +656,19 @@ var
'value="' + IntToStr(Value) + '">';
end;
function DateInput(const FieldName, Value: string; const AIdx: Integer; const MinWidth: Integer = 0): string;
function DateInput(const FieldName, Value: string; const AIdx: Integer): string;
var
w: string;
dateValue: string;
begin
w := '';
if MinWidth > 0 then
w := ' style="min-width: ' + IntToStr(MinWidth) + 'px;"';
dateValue := Utils.NormalizeDateValue(Value);
Result :=
'<input type="date" class="form-control form-control-sm cell-input task-editor w-100" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '" ' +
'value="' + HtmlEncode(Value) + '"' + w + '>';
'value="' + HtmlEncode(dateValue) + '"' + w + '>';
end;
function SelectList(const FieldName, Current: string; const AIdx: Integer; const Items: TJSArray; const AllowEdit: Boolean): string;
......@@ -681,13 +676,21 @@ var
i: Integer;
itemText: string;
triggerId: string;
buttonClass: string;
begin
triggerId := 'task_dd_' + FieldName + '_' + IntToStr(AIdx);
buttonClass := 'btn btn-sm border w-100 d-flex justify-content-between align-items-center text-start task-dd-toggle ';
if SameText(FieldName, 'status') then
buttonClass := buttonClass + GetTaskStatusClass(Current)
else
buttonClass := buttonClass + 'btn-light';
Result :=
'<div class="dropdown w-100">' +
'<button id="' + triggerId + '" class="btn btn-sm btn-light border w-100 d-flex justify-content-between align-items-center text-start task-dd-toggle" ' +
'type="button" data-bs-toggle="dropdown" aria-expanded="false">' +
'<button id="' + triggerId + '" class="' + buttonClass + '" ' +
'type="button" data-bs-toggle="dropdown" aria-expanded="false" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '">' +
'<span class="task-dd-label text-truncate">' + HtmlEncode(Current) + '</span>' +
'<span class="dropdown-toggle dropdown-toggle-split border-0 ms-2"></span>' +
'</button>' +
......@@ -728,31 +731,6 @@ var
'</div>';
end;
function StatusSelect(const Current: string; const AIdx: Integer): string;
var
i: Integer;
statusText: string;
sel: string;
begin
Result :=
'<select class="form-select form-select-sm task-select" data-idx="' + IntToStr(AIdx) + '" data-field="status">';
Result := Result + '<option value=""></option>'; //Note: This adds the blank option on top
if Assigned(FStatusOptions) then
for i := 0 to FStatusOptions.length - 1 do
begin
statusText := string(FStatusOptions[i]);
sel := '';
if SameText(Current, statusText) then
sel := ' selected';
Result := Result + '<option value="' + HtmlEncode(statusText) + '"' + sel + '>' + HtmlEncode(statusText) + '</option>';
end;
Result := Result + '</select>';
end;
begin
host := TJSHTMLElement(document.getElementById('tasks_table_host'));
if not Assigned(host) then
......@@ -761,12 +739,12 @@ begin
html :=
'<div class="tasks-vscroll">' +
'<div class="tasks-hscroll">' +
'<table class="table table-sm table-bordered align-middle mb-0" style="min-width: 1700px;">' +
'<table class="table table-sm table-bordered align-middle mb-0 tasks-table" style="min-width: 1700px; table-layout: fixed;">' +
'<colgroup>' +
'<col style="width:40px">' + // Item Num
'<col style="width:200px">' + // App
'<col style="width:90px">' + // Version
'<col style="width:120px">' + // Date
'<col style="width:140px">' + // Date
'<col style="width:120px">' + // Reported
'<col style="width:120px">' + // Assigned
'<col style="width:195px">' + // Status
......@@ -797,13 +775,13 @@ begin
'<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(SelectList('application', xdwdsTasksapplication.AsString, rowIdx, FApplicationOptions, True)) +
TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx, 80)) +
TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx, 110)) +
TdNowrap(SelectList('reportedBy', xdwdsTasksreportedBy.AsString, rowIdx, FReportedByOptions, False)) +
TdNowrap(TextInput('version', xdwdsTasksversion.AsString, rowIdx)) +
TdNowrap(DateInput('taskDate', xdwdsTaskstaskDate.AsString, rowIdx)) +
TdNowrap(SelectList('reportedBy', xdwdsTasksreportedBy.AsString, rowIdx, FReportedByOptions, True)) +
TdNowrap(SelectList('assignedTo', xdwdsTasksassignedTo.AsString, rowIdx, FAssignedToOptions, True)) +
TdNowrap(StatusSelect(xdwdsTasksstatus.AsString, rowIdx)) +
TdNowrap(DateInput('statusDate', xdwdsTasksstatusDate.AsString, rowIdx, 110)) +
TdNowrap(TextInput('formSection', xdwdsTasksformSection.AsString, rowIdx, 160)) +
TdNowrap(SelectList('status', xdwdsTasksstatus.AsString, rowIdx, FStatusOptions, False)) +
TdNowrap(DateInput('statusDate', xdwdsTasksstatusDate.AsString, rowIdx)) +
TdNowrap(TextInput('formSection', xdwdsTasksformSection.AsString, rowIdx)) +
TdWrap(TextArea('issue', xdwdsTasksissue.AsString, rowIdx)) +
TdWrap(TextArea('notes', xdwdsTasksnotes.AsString, rowIdx)) +
'</tr>';
......@@ -830,12 +808,16 @@ begin
(function(){
const host = document.getElementById('tasks_table_host');
if(!host) return;
const table = host.querySelector('table');
if(!table) return;
const ths = table.querySelectorAll('thead th');
ths.forEach(th => {
const cols = table.querySelectorAll('colgroup col');
ths.forEach((th, index) => {
th.classList.add('th-resize');
if(th.querySelector('.th-resize-handle')) return;
if (th.querySelector('.th-resize-handle')) return;
const handle = document.createElement('div');
handle.className = 'th-resize-handle';
......@@ -843,17 +825,27 @@ begin
handle.addEventListener('mousedown', function(e){
e.preventDefault();
e.stopPropagation();
const startX = e.clientX;
const startW = th.getBoundingClientRect().width;
function onMove(ev){
const w = Math.max(40, startW + (ev.clientX - startX));
const w = Math.max(20, startW + (ev.clientX - startX));
th.style.width = w + 'px';
th.style.minWidth = '0';
if (cols && cols[index]) {
cols[index].style.width = w + 'px';
}
}
function onUp(){
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
......@@ -896,7 +888,7 @@ begin
end;
console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName);
SaveRow(idx);
SaveField(idx, fieldName);
end;
......@@ -941,14 +933,13 @@ begin
end;
[async] procedure TFTaskItems.SaveRow(AIndex: Integer);
[async] procedure TFTaskItems.SaveField(AIndex: Integer; const AFieldName: string);
const
// Note: Use this to manipulate saving to the server or not for testing
ENABLE_SERVER_SAVE = True;
var
response: TXDataClientResponse;
payload: TJSObject;
payloadJson: string;
fieldValue: string;
begin
if not xdwdsTasks.Active then
Exit;
......@@ -957,35 +948,47 @@ begin
if xdwdsTasks.Eof then
Exit;
payload := TJSObject.new;
fieldValue := '';
if SameText(AFieldName, 'application') then
fieldValue := xdwdsTasksapplication.AsString
else if SameText(AFieldName, 'version') then
fieldValue := xdwdsTasksversion.AsString
else if SameText(AFieldName, 'taskDate') then
fieldValue := xdwdsTaskstaskDate.AsString
else if SameText(AFieldName, 'reportedBy') then
fieldValue := xdwdsTasksreportedBy.AsString
else if SameText(AFieldName, 'assignedTo') then
fieldValue := xdwdsTasksassignedTo.AsString
else if SameText(AFieldName, 'status') then
fieldValue := xdwdsTasksstatus.AsString
else if SameText(AFieldName, 'statusDate') then
fieldValue := xdwdsTasksstatusDate.AsString
else if SameText(AFieldName, 'formSection') then
fieldValue := xdwdsTasksformSection.AsString
else if SameText(AFieldName, 'issue') then
fieldValue := xdwdsTasksissue.AsString
else if SameText(AFieldName, 'notes') then
fieldValue := xdwdsTasksnotes.AsString
else
Exit;
payload := TJSObject.new;
payload['taskItemId'] := xdwdsTaskstaskItemId.AsInteger;
payload['taskId'] := xdwdsTaskstaskId.AsString;
payload['application'] := xdwdsTasksapplication.AsString;
payload['version'] := xdwdsTasksversion.AsString;
payload['taskDate'] := xdwdsTaskstaskDate.AsString;
payload['reportedBy'] := xdwdsTasksreportedBy.AsString;
payload['assignedTo'] := xdwdsTasksassignedTo.AsString;
payload['status'] := xdwdsTasksstatus.AsString;
payload['statusDate'] := xdwdsTasksstatusDate.AsString;
payload['formSection'] := xdwdsTasksformSection.AsString;
payload['issue'] := xdwdsTasksissue.AsString;
payload['notes'] := xdwdsTasksnotes.AsString;
payloadJson := string(TJSJSON.stringify(payload));
console.log('SaveRow: idx=' + IntToStr(AIndex) + ' payload=' + payloadJson);
payload['fieldName'] := AFieldName;
payload['value'] := fieldValue;
console.log('SaveField: idx=' + IntToStr(AIndex) + ' field=' + AFieldName + ' value=' + fieldValue);
if not ENABLE_SERVER_SAVE then
Exit;
try
response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskRow', [payload]));
console.log('SaveRow: response=' + string(TJSJSON.stringify(response.Result)));
response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskItemField', [payload]));
console.log('SaveField: response=' + string(TJSJSON.stringify(response.Result)));
except
on E: EXDataClientRequestException do
begin
console.log('SaveRow ERROR: ' + E.ErrorResult.ErrorMessage);
console.log('SaveField ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
end;
......@@ -1017,75 +1020,14 @@ begin
end;
end;
function TFTaskItems.ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['name']));
end;
end;
function TFTaskItems.ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['codeDesc']));
end;
end;
function TFTaskItems.GetOptionsForField(const AFieldName: string): TJSArray;
begin
if SameText(AFieldName, 'application') then
Result := FApplicationOptions
else if SameText(AFieldName, 'reportedBy') then
Result := FReportedByOptions
else if SameText(AFieldName, 'assignedTo') then
Result := FAssignedToOptions
else
Result := nil;
end;
procedure TFTaskItems.FocusTrigger(const ATriggerId: string);
var
el: TJSHTMLElement;
begin
if ATriggerId = '' then
Exit;
el := TJSHTMLElement(document.getElementById(ATriggerId));
if Assigned(el) then
el.focus;
end;
procedure TFTaskItems.DropdownItemClick(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr, fieldName, newVal, triggerId: string;
btn: TJSHTMLElement;
labelEl: TJSHTMLElement;
begin
if not xdwdsTasks.Active then
Exit;
......@@ -1113,18 +1055,19 @@ begin
if triggerId <> '' then
begin
asm
var btn = document.getElementById(triggerId);
if (btn) {
var labelEl = btn.querySelector('.task-dd-label');
if (labelEl) {
labelEl.textContent = newVal;
}
}
btn := TJSHTMLElement(document.getElementById(triggerId));
if Assigned(btn) then
begin
labelEl := TJSHTMLElement(btn.querySelector('.task-dd-label'));
if Assigned(labelEl) then
labelEl.textContent := newVal;
if SameText(fieldName, 'status') then
ApplyTaskStatusClass(btn, newVal);
end;
end;
SaveRow(idx);
SaveField(idx, fieldName);
end;
......@@ -1147,7 +1090,7 @@ begin
if (idx < 0) or (fieldName = '') then
Exit;
if not (SameText(fieldName, 'assignedTo') or SameText(fieldName, 'application')) then
if not (SameText(fieldName, 'assignedTo') or SameText(fieldName, 'application') or SameText(fieldName, 'reportedBy')) then
Exit;
FNameManager.OpenManager(fieldName, idx, triggerId);
......@@ -1209,10 +1152,13 @@ begin
FSelectedTaskId := StrToIntDef(taskIdStr, 0);
btnDeleteRow.Enabled := FSelectedTaskItemId > 0;
ApplySelectedRowState;
asm
rowEl.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' });
end;
end;
asm
el.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' });
el.focus();
end;
end;
......@@ -1244,40 +1190,6 @@ begin
end;
function TFTaskItems.ExtractApplicationOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['applicationOptions']));
end;
function TFTaskItems.ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['assignedToOptions']));
end;
function TFTaskItems.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;
[async] function TFTaskItems.AddAssignedName(const AName: string): TJSArray;
var
response: TXDataClientResponse;
......@@ -1366,6 +1278,8 @@ begin
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;
......@@ -1374,6 +1288,8 @@ begin
if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
......@@ -1393,7 +1309,7 @@ begin
xdwdsTasks.Post;
RenderTable;
await(SaveRow(ARowIndex));
await(SaveField(ARowIndex, AFieldName));
end;
[async] procedure TFTaskItems.HandleRenameManagedName(const AFieldName, AOldName, ANewName: string);
......@@ -1404,6 +1320,8 @@ begin
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;
......@@ -1412,6 +1330,8 @@ begin
if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
......@@ -1427,6 +1347,8 @@ begin
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;
......@@ -1435,6 +1357,8 @@ begin
if SameText(AFieldName, 'application') then
FApplicationOptions := newOptions
else if SameText(AFieldName, 'reportedBy') then
FReportedByOptions := newOptions
else
FAssignedToOptions := newOptions;
......@@ -1521,6 +1445,85 @@ begin
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;
end.
......
......@@ -62,6 +62,9 @@ input[data-field="itemNum"] {
top: 0;
z-index: 2;
background: var(--bs-body-bg);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tasks-vscroll thead th.th-resize {
......@@ -86,7 +89,155 @@ span.card {
user-select: none;
}
. test-test {}
.tasks-table {
table-layout: fixed;
}
.tasks-table th {
overflow: hidden;
}
.tasks-table td {
overflow: visible;
}
.nowrap-cell,
.wrap-cell {
overflow: visible;
}
.tasks-table .dropdown,
.task-dd-toggle,
.task-dd-label,
.cell-input,
.cell-textarea {
min-width: 0;
}
.dropdown-menu {
z-index: 1055;
}
.task-dd-toggle.status-cannot-duplicate {
--bs-btn-color: #41464b;
--bs-btn-bg: #e2e3e5;
--bs-btn-border-color: #c4c8cb;
--bs-btn-hover-color: #41464b;
--bs-btn-hover-bg: #d3d4d5;
--bs-btn-hover-border-color: #b9bcc0;
--bs-btn-active-color: #41464b;
--bs-btn-active-bg: #c6c7c8;
--bs-btn-active-border-color: #b9bcc0;
}
.task-dd-toggle.status-cannot-test {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-future-enhancement {
--bs-btn-color: #055160;
--bs-btn-bg: #cff4fc;
--bs-btn-border-color: #9eeaf9;
--bs-btn-hover-color: #055160;
--bs-btn-hover-bg: #b6effb;
--bs-btn-hover-border-color: #86e5f8;
--bs-btn-active-color: #055160;
--bs-btn-active-bg: #9eeaf9;
--bs-btn-active-border-color: #74dff6;
}
.task-dd-toggle.status-fixed-verified {
--bs-btn-color: #0f5132;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #0f5132;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #0f5132;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-fixed {
--bs-btn-color: #146c43;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #146c43;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #146c43;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-investigating {
--bs-btn-color: #664d03;
--bs-btn-bg: #fff3cd;
--bs-btn-border-color: #ffe69c;
--bs-btn-hover-color: #664d03;
--bs-btn-hover-bg: #ffecb5;
--bs-btn-hover-border-color: #ffdf7e;
--bs-btn-active-color: #664d03;
--bs-btn-active-bg: #ffe69c;
--bs-btn-active-border-color: #ffd966;
}
.task-dd-toggle.status-not-fixed {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-non-issue {
--bs-btn-color: #432874;
--bs-btn-bg: #e2d9f3;
--bs-btn-border-color: #cbbbe8;
--bs-btn-hover-color: #432874;
--bs-btn-hover-bg: #d6caee;
--bs-btn-hover-border-color: #bea9e2;
--bs-btn-active-color: #432874;
--bs-btn-active-bg: #cbbbe8;
--bs-btn-active-border-color: #b89ddd;
}
.task-dd-toggle.status-possibly-a-problem {
--bs-btn-color: #7a3e00;
--bs-btn-bg: #ffe5d0;
--bs-btn-border-color: #f7c79d;
--bs-btn-hover-color: #7a3e00;
--bs-btn-hover-bg: #ffd7b8;
--bs-btn-hover-border-color: #f2ba88;
--bs-btn-active-color: #7a3e00;
--bs-btn-active-bg: #f7c79d;
--bs-btn-active-border-color: #eeaf69;
}
.task-dd-toggle.status-default {
--bs-btn-color: #212529;
--bs-btn-bg: #f8f9fa;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #212529;
--bs-btn-hover-bg: #e9ecef;
--bs-btn-hover-border-color: #ced4da;
--bs-btn-active-color: #212529;
--bs-btn-active-bg: #dee2e6;
--bs-btn-active-border-color: #ced4da;
}
......@@ -12,7 +12,8 @@ uses
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
Utils in 'Utils.pas',
View.TaskItems in 'View.TaskItems.pas' {FTaskItems: TWebForm} {*.html},
uNameManager in 'uNameManager.pas';
uNameOffCanvas in 'uNameOffCanvas.pas',
uDropdownHelpers in 'uDropdownHelpers.pas';
{$R *.res}
......
......@@ -94,12 +94,12 @@
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.8.7.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.8.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.8.8.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.8.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>8</VerInfo_MinorVer>
<VerInfo_Release>7</VerInfo_Release>
<TMSWebBrowser>1</TMSWebBrowser>
<VerInfo_Release>8</VerInfo_Release>
<TMSWebBrowser>5</TMSWebBrowser>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
......@@ -142,7 +142,8 @@
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="uNameManager.pas"/>
<DCCReference Include="uNameOffCanvas.pas"/>
<DCCReference Include="uDropdownHelpers.pas"/>
<None Include="index.html"/>
<None Include="css\app.css"/>
<None Include="config\config.json"/>
......
unit uDropdownHelpers;
interface
uses
System.SysUtils, System.Classes, JS, Web,
WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs;
function GetTaskStatusClass(const AStatus: string): string;
procedure ClearTaskStatusClasses(const AElement: TJSHTMLElement);
procedure ApplyTaskStatusClass(const AElement: TJSHTMLElement; const AStatus: string);
function ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
function ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
function ExtractReportedOptionNames(const ResponseResult: TJSObject): TJSArray;
function ExtractApplicationOptionNames(const ResponseResult: TJSObject): TJSArray;
function FindOptionIgnoreCase(const AItems: TJSArray; const AName: string): string;
function GetOptionsForField(const AFieldName: string; const AApplicationOptions, AReportedByOptions, AAssignedToOptions: TJSArray): TJSArray;
procedure FocusTrigger(const ATriggerId: string);
implementation
function GetTaskStatusClass(const AStatus: string): string;
var
statusText: string;
begin
statusText := Trim(AStatus);
if SameText(statusText, 'Cannot Duplicate') then
Result := 'status-cannot-duplicate'
else if SameText(statusText, 'Cannot Test') then
Result := 'status-cannot-test'
else if SameText(statusText, 'Future Enhancement') then
Result := 'status-future-enhancement'
else if SameText(statusText, 'Fixed - Verified') then
Result := 'status-fixed-verified'
else if SameText(statusText, 'Fixed') then
Result := 'status-fixed'
else if SameText(statusText, 'Investigating') then
Result := 'status-investigating'
else if SameText(statusText, 'Not Fixed') then
Result := 'status-not-fixed'
else if SameText(statusText, 'Non-Issue') then
Result := 'status-non-issue'
else if SameText(statusText, 'Possibly a Problem') then
Result := 'status-possibly-a-problem'
else
Result := 'status-default';
end;
procedure ClearTaskStatusClasses(const AElement: TJSHTMLElement);
begin
if not Assigned(AElement) then
Exit;
AElement.classList.remove('status-cannot-duplicate');
AElement.classList.remove('status-cannot-test');
AElement.classList.remove('status-future-enhancement');
AElement.classList.remove('status-fixed-verified');
AElement.classList.remove('status-fixed');
AElement.classList.remove('status-investigating');
AElement.classList.remove('status-not-fixed');
AElement.classList.remove('status-non-issue');
AElement.classList.remove('status-possibly-a-problem');
AElement.classList.remove('status-default');
end;
procedure ApplyTaskStatusClass(const AElement: TJSHTMLElement; const AStatus: string);
begin
if not Assigned(AElement) then
Exit;
ClearTaskStatusClasses(AElement);
AElement.classList.add(GetTaskStatusClass(AStatus));
end;
function ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['name']));
end;
end;
function ExtractCodeDescs(const SourceArray: TJSArray): TJSArray;
var
i: Integer;
optionObj: TJSObject;
begin
Result := TJSArray.new;
if not Assigned(SourceArray) then
Exit;
for i := 0 to SourceArray.length - 1 do
begin
optionObj := TJSObject(SourceArray[i]);
if Assigned(optionObj) then
Result.push(string(optionObj['codeDesc']));
end;
end;
function ExtractAssignedOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['assignedToOptions']));
end;
function ExtractReportedOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['reportedByOptions']));
end;
function ExtractApplicationOptionNames(const ResponseResult: TJSObject): TJSArray;
begin
if not Assigned(ResponseResult) then
Exit(TJSArray.new);
Result := ExtractOptionNames(TJSArray(ResponseResult['applicationOptions']));
end;
function FindOptionIgnoreCase(const AItems: TJSArray; const AName: string): string;
var
i: Integer;
itemText: string;
begin
Result := '';
if not Assigned(AItems) then
Exit;
for i := 0 to AItems.length - 1 do
begin
itemText := string(AItems[i]);
if SameText(itemText, Trim(AName)) then
Exit(itemText);
end;
end;
function GetOptionsForField(const AFieldName: string; const AApplicationOptions, AReportedByOptions, AAssignedToOptions: TJSArray): TJSArray;
begin
if SameText(AFieldName, 'application') then
Result := AApplicationOptions
else if SameText(AFieldName, 'reportedBy') then
Result := AReportedByOptions
else if SameText(AFieldName, 'assignedTo') then
Result := AAssignedToOptions
else
Result := nil;
end;
procedure FocusTrigger(const ATriggerId: string);
var
el: TJSHTMLElement;
begin
if ATriggerId = '' then
Exit;
el := TJSHTMLElement(document.getElementById(ATriggerId));
if Assigned(el) then
el.focus;
end;
end.
unit uNameManager;
unit uNameOffCanvas;
interface
......@@ -257,7 +257,7 @@ begin
Exit;
end;
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application')) then
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit;
if FCurrentEditName <> '' then
......@@ -356,7 +356,7 @@ begin
Event.preventDefault;
Event.stopPropagation;
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application')) then
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit;
el := TJSHTMLElement(Event.currentTarget);
......@@ -383,7 +383,7 @@ begin
Event.preventDefault;
Event.stopPropagation;
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application')) then
if not (SameText(FCurrentField, 'assignedTo') or SameText(FCurrentField, 'application') or SameText(FCurrentField, 'reportedBy')) then
Exit;
el := TJSHTMLElement(Event.currentTarget);
......@@ -401,7 +401,7 @@ procedure TNameManager.OpenManager(const AFieldName: string; const ARowIndex: In
var
titleEl: TJSHTMLElement;
begin
if not (SameText(AFieldName, 'assignedTo') or SameText(AFieldName, 'application')) then
if not (SameText(AFieldName, 'assignedTo') or SameText(AFieldName, 'application') or SameText(AFieldName, 'reportedBy')) then
Exit;
FCurrentField := AFieldName;
......@@ -414,6 +414,8 @@ begin
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;
......
object ApiDatabase: TApiDatabase
OnCreate = DataModuleCreate
Height = 475
Width = 803
Width = 996
object ucETaskApi: TUniConnection
AutoCommit = False
ProviderName = 'MySQL'
......@@ -236,17 +236,17 @@ object ApiDatabase: TApiDatabase
'values ('
' :TASK_ID,'
' :ITEM_NUM,'
' '#39#39','
' '#39#39','
' curdate(),'
' curdate(),'
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39','
' '#39#39
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null,'
' null'
')')
Left = 536
Top = 282
......@@ -580,7 +580,8 @@ object ApiDatabase: TApiDatabase
object uqProjectReportedUsers: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select distinct'
'select'
' min(tiu.TASK_ITEM_USER_ID) as TASK_ITEM_USER_ID,'
' tiu.NAME'
'from task_item_user tiu'
'join tasks project_tasks'
......@@ -589,6 +590,7 @@ object ApiDatabase: TApiDatabase
' on target_task.PROJECT_ID = project_tasks.PROJECT_ID'
'where target_task.TASK_ID = :TASK_ID'
' and tiu.USER_TYPE = '#39'Reported'#39
'group by tiu.NAME'
'order by tiu.NAME')
Left = 58
Top = 256
......@@ -598,6 +600,11 @@ object ApiDatabase: TApiDatabase
Name = 'TASK_ID'
Value = nil
end>
object uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField
FieldName = 'TASK_ITEM_USER_ID'
ReadOnly = True
Size = 50
end
object uqProjectReportedUsersNAME: TStringField
FieldName = 'NAME'
Required = True
......@@ -758,7 +765,7 @@ object ApiDatabase: TApiDatabase
Connection = ucETaskApi
SQL.Strings = (
'update task_items'
'set ASSIGNED_TO = '#39#39
'set ASSIGNED_TO = null'
'where TASK_ID = :TASK_ID'
' and lower(ASSIGNED_TO) = lower(:NAME)')
Left = 388
......@@ -914,7 +921,7 @@ object ApiDatabase: TApiDatabase
'update task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'set ti.APPLICATION = '#39#39
'set ti.APPLICATION = null'
'where t.PROJECT_ID = :PROJECT_ID'
' and lower(ti.APPLICATION) = lower(:NAME)')
Left = 696
......@@ -931,4 +938,150 @@ object ApiDatabase: TApiDatabase
Value = nil
end>
end
object uqReportedInsert: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'insert into task_item_user ('
' TASK_ITEM_USER_ID,'
' TASK_ID,'
' USER_TYPE,'
' NAME'
')'
'values ('
' :TASK_ITEM_USER_ID,'
' :TASK_ID,'
' '#39'Reported'#39','
' :NAME'
')')
Left = 858
Top = 32
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ITEM_USER_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'NAME'
Value = nil
end>
end
object uqReportedRename: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_item_user tiu'
'join tasks t'
' on t.TASK_ID = tiu.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'set tiu.NAME = :NEW_NAME'
'where target_task.TASK_ID = :TASK_ID'
' and tiu.USER_TYPE = '#39'Reported'#39
' and tiu.TASK_ITEM_USER_ID = :TASK_ITEM_USER_ID')
Left = 858
Top = 88
ParamData = <
item
DataType = ftUnknown
Name = 'NEW_NAME'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ITEM_USER_ID'
Value = nil
end>
end
object uqReportedDelete: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'delete tiu'
'from task_item_user tiu'
'join tasks t'
' on t.TASK_ID = tiu.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'where target_task.TASK_ID = :TASK_ID'
' and tiu.USER_TYPE = '#39'Reported'#39
' and tiu.TASK_ITEM_USER_ID = :TASK_ITEM_USER_ID')
Left = 858
Top = 144
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ITEM_USER_ID'
Value = nil
end>
end
object uqBlankTaskItemReportedBy: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'set ti.REPORTED_BY = null'
'where target_task.TASK_ID = :TASK_ID'
' and lower(ti.REPORTED_BY) = lower(:NAME)')
Left = 860
Top = 264
ParamData = <
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'NAME'
Value = nil
end>
end
object uqRenameTaskItemReportedBy: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'update task_items ti'
'join tasks t'
' on t.TASK_ID = ti.TASK_ID'
'join tasks target_task'
' on target_task.PROJECT_ID = t.PROJECT_ID'
'set ti.REPORTED_BY = :NEW_NAME'
'where target_task.TASK_ID = :TASK_ID'
' and lower(ti.REPORTED_BY) = lower(:OLD_NAME)')
Left = 858
Top = 202
ParamData = <
item
DataType = ftUnknown
Name = 'NEW_NAME'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'OLD_NAME'
Value = nil
end>
end
end
......@@ -68,7 +68,6 @@ type
uqAssignedDelete: TUniQuery;
uqRenameAssignedTo: TUniQuery;
uqBlankAssignedTo: TUniQuery;
uqProjectReportedUsersNAME: TStringField;
uqTaskAssignedUsersTASK_ITEM_USER_ID: TStringField;
uqTaskAssignedUsersTASK_ID: TStringField;
uqTaskAssignedUsersUSER_TYPE: TStringField;
......@@ -82,6 +81,13 @@ type
uqProjectApplicationsTASK_ITEM_APPLICATION_ID: TIntegerField;
uqProjectApplicationsPROJECT_ID: TStringField;
uqProjectApplicationsNAME: TStringField;
uqReportedInsert: TUniQuery;
uqReportedRename: TUniQuery;
uqReportedDelete: TUniQuery;
uqBlankTaskItemReportedBy: TUniQuery;
uqRenameTaskItemReportedBy: TUniQuery;
uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField;
uqProjectReportedUsersNAME: TStringField;
procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet);
private
......
......@@ -76,6 +76,7 @@ type
TTaskUserOptionsResponse = class
public
assignedToOptions: TList<TTaskUserOption>;
reportedByOptions: TList<TTaskUserOption>;
constructor Create;
destructor Destroy; override;
end;
......@@ -105,21 +106,37 @@ type
notes: string;
end;
TTaskItemFieldSave = class
public
taskItemId: Integer;
fieldName: string;
value: string;
end;
type
[ServiceContract, Model(API_MODEL)]
IApiService = interface(IInvokable)
['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}']
function GetTaskItems(taskId: string): TTaskItemsResponse;
[HttpPost] function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
[HttpPost] function SaveTaskRow(Item: TTaskRowSave): Boolean;
[HttpPost] function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost] function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
[HttpPost] function DeleteAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost] function AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost]function RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
[HttpPost]function DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
[HttpPost] function AddApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
[HttpPost] function RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
[HttpPost] function DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
function DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
end;
implementation
......@@ -148,11 +165,13 @@ constructor TTaskUserOptionsResponse.Create;
begin
inherited;
assignedToOptions := TList<TTaskUserOption>.Create;
reportedByOptions := TList<TTaskUserOption>.Create;
end;
destructor TTaskUserOptionsResponse.Destroy;
begin
assignedToOptions.Free;
assignedToOptions := TList<TTaskUserOption>.Create;
reportedByOptions := TList<TTaskUserOption>.Create;
inherited;
end;
......
......@@ -3,14 +3,10 @@ unit Api.ServiceImpl;
interface
uses
XData.Server.Module,
XData.Service.Common,
System.Variants,
System.DateUtils,
Api.Service,
Api.Database,
Common.Logging,
System.SysUtils, JSON, System.IniFiles;
XData.Server.Module, XData.Service.Common,
System.Variants, System.DateUtils, Uni,
Api.Service, Api.Database, Common.Logging,
System.SysUtils, JSON, System.IniFiles, DBAccess;
type
[ServiceImplementation]
......@@ -23,7 +19,6 @@ type
function FindAssignedOptionId(const taskId, name: string): string;
function FindAssignedOptionName(const taskId, name: string): string;
function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
function SaveTaskRow(Item: TTaskRowSave): Boolean;
function BuildAssignedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
function RenameAssignedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
......@@ -37,6 +32,13 @@ type
function AddApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
function RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
function DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
function FindReportedOptionId(const taskId, name: string): string;
function FindReportedOptionName(const taskId, name: string): string;
function BuildReportedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
function AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
function RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
function DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
......@@ -366,6 +368,116 @@ begin
end;
function TApiService.SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
var
uqSaveField: TUniQuery;
columnName: string;
d: TDateTime;
function MapFieldNameToColumn(const AFieldName: string): string;
begin
Result := '';
if SameText(AFieldName, 'application') then
Result := 'APPLICATION'
else if SameText(AFieldName, 'version') then
Result := 'APP_VERSION'
else if SameText(AFieldName, 'taskDate') then
Result := 'TASK_DATE'
else if SameText(AFieldName, 'reportedBy') then
Result := 'REPORTED_BY'
else if SameText(AFieldName, 'assignedTo') then
Result := 'ASSIGNED_TO'
else if SameText(AFieldName, 'status') then
Result := 'STATUS'
else if SameText(AFieldName, 'statusDate') then
Result := 'STATUS_DATE'
else if SameText(AFieldName, 'formSection') then
Result := 'FORM_SECTION'
else if SameText(AFieldName, 'issue') then
Result := 'ISSUE'
else if SameText(AFieldName, 'notes') then
Result := 'NOTES'
else if SameText(AFieldName, 'fixedVersion') then
Result := 'FIXED_VERSION';
end;
function IsDateField(const AFieldName: string): Boolean;
begin
Result := SameText(AFieldName, 'taskDate') or SameText(AFieldName, 'statusDate');
end;
function ParseDateOrZero(const S: string; out ADate: TDateTime): Boolean;
var
y, m, dInt: Word;
begin
Result := False;
ADate := 0;
if Length(Trim(S)) <> 10 then
Exit;
y := StrToIntDef(Copy(S, 1, 4), 0);
m := StrToIntDef(Copy(S, 6, 2), 0);
dInt := StrToIntDef(Copy(S, 9, 2), 0);
if (y > 0) and (m > 0) and (dInt > 0) then
begin
try
ADate := EncodeDate(y, m, dInt);
Result := True;
except
Result := False;
end;
end;
end;
begin
Result := False;
if not Assigned(Item) then
raise Exception.Create('SaveTaskItemField: Item is nil.');
if Item.taskItemId <= 0 then
raise Exception.Create('SaveTaskItemField: Invalid taskItemId.');
columnName := MapFieldNameToColumn(Item.fieldName);
if columnName = '' then
raise Exception.Create('SaveTaskItemField: Invalid field name: ' + Item.fieldName);
uqSaveField := TUniQuery.Create(nil);
try
uqSaveField.Connection := apiDB.ucETaskApi;
uqSaveField.SQL.Text :=
'update task_items ' +
'set ' + columnName + ' = :' + columnName + ' ' +
'where TASK_ITEM_ID = :TASK_ITEM_ID';
uqSaveField.ParamByName('TASK_ITEM_ID').AsInteger := Item.taskItemId;
if IsDateField(Item.fieldName) then
begin
if ParseDateOrZero(Item.value, d) then
uqSaveField.ParamByName(columnName).AsDateTime := d
else
uqSaveField.ParamByName(columnName).Clear;
end
else
begin
if Trim(Item.value) = '' then
uqSaveField.ParamByName(columnName).Clear
else
uqSaveField.ParamByName(columnName).AsString := Item.value;
end;
uqSaveField.ExecSQL;
Result := True;
finally
uqSaveField.Free;
end;
end;
function TApiService.BuildTaskNumber: string;
procedure AddPart(const value: string);
......@@ -409,62 +521,6 @@ begin
end;
function TApiService.SaveTaskRow(Item: TTaskRowSave): Boolean;
function ParseDateOrZero(const S: string; out D: TDateTime): Boolean;
begin
Result := Trim(S) <> '';
if Result then
D := ISO8601ToDate(S, False);
end;
var
d: TDateTime;
begin
Logger.Log(4, Format('ApiService.SaveTaskRow - TASK_ITEM_ID="%d"', [Item.taskItemId]));
try
apiDB.uqSaveTaskRow.Close;
apiDB.uqSaveTaskRow.ParamByName('TASK_ITEM_ID').AsInteger := Item.taskItemId;
apiDB.uqSaveTaskRow.ParamByName('APPLICATION').AsString := Item.application;
apiDB.uqSaveTaskRow.ParamByName('APP_VERSION').AsString := Item.version;
if ParseDateOrZero(Item.taskDate, d) then
apiDB.uqSaveTaskRow.ParamByName('TASK_DATE').AsDateTime := d
else
apiDB.uqSaveTaskRow.ParamByName('TASK_DATE').Clear;
apiDB.uqSaveTaskRow.ParamByName('REPORTED_BY').AsString := Item.reportedBy;
apiDB.uqSaveTaskRow.ParamByName('ASSIGNED_TO').AsString := Item.assignedTo;
apiDB.uqSaveTaskRow.ParamByName('STATUS').AsString := Item.status;
if ParseDateOrZero(Item.statusDate, d) then
apiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := d
else
apiDB.uqSaveTaskRow.ParamByName('STATUS_DATE').AsDateTime := Date;
apiDB.uqSaveTaskRow.ParamByName('FIXED_VERSION').AsString := Item.fixedVersion;
apiDB.uqSaveTaskRow.ParamByName('FORM_SECTION').AsString := Item.formSection;
apiDB.uqSaveTaskRow.ParamByName('ISSUE').AsString := Item.issue;
apiDB.uqSaveTaskRow.ParamByName('NOTES').AsString := Item.notes;
apiDB.uqSaveTaskRow.ExecSQL;
Result := True;
Logger.Log(4, 'ApiService.SaveTaskRow - OK');
except
on E: Exception do
begin
Logger.Log(2, 'ApiService.SaveTaskRow - ERROR: ' + E.Message);
raise;
end;
end;
end;
function TApiService.DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
var
oldItemNum: Integer;
......@@ -934,6 +990,203 @@ begin
Result := BuildApplicationOptionsResponse(taskId);
end;
function TApiService.FindReportedOptionId(const taskId, name: string): string;
begin
Result := '';
apiDB.uqProjectReportedUsers.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open;
while not apiDB.uqProjectReportedUsers.Eof do
begin
if SameText(Trim(apiDB.uqProjectReportedUsersNAME.AsString), Trim(name)) then
begin
Result := apiDB.uqProjectReportedUsersTASK_ITEM_USER_ID.AsString;
Exit;
end;
apiDB.uqProjectReportedUsers.Next;
end;
end;
function TApiService.FindReportedOptionName(const taskId, name: string): string;
begin
Result := '';
apiDB.uqProjectReportedUsers.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open;
while not apiDB.uqProjectReportedUsers.Eof do
begin
if SameText(Trim(apiDB.uqProjectReportedUsersNAME.AsString), Trim(name)) then
begin
Result := apiDB.uqProjectReportedUsersNAME.AsString;
Exit;
end;
apiDB.uqProjectReportedUsers.Next;
end;
end;
function TApiService.BuildReportedOptionsResponse(const taskId: string): TTaskUserOptionsResponse;
var
taskUserOption: TTaskUserOption;
begin
Result := TTaskUserOptionsResponse.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
apiDB.uqProjectReportedUsers.Close;
apiDB.uqProjectReportedUsers.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqProjectReportedUsers.Open;
while not apiDB.uqProjectReportedUsers.Eof do
begin
taskUserOption := TTaskUserOption.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(taskUserOption);
taskUserOption.name := apiDB.uqProjectReportedUsersNAME.AsString;
Result.reportedByOptions.Add(taskUserOption);
apiDB.uqProjectReportedUsers.Next;
end;
end;
function TApiService.AddReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
newName: string;
existingName: string;
newGuid: TGuid;
begin
newName := Trim(name);
if newName = '' then
raise Exception.Create('Reported name cannot be blank.');
existingName := FindReportedOptionName(taskId, newName);
if existingName = '' then
begin
CreateGUID(newGuid);
apiDB.uqReportedInsert.Connection.StartTransaction;
try
apiDB.uqReportedInsert.Close;
apiDB.uqReportedInsert.ParamByName('TASK_ITEM_USER_ID').AsString := GuidToString(newGuid);
apiDB.uqReportedInsert.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedInsert.ParamByName('NAME').AsString := newName;
apiDB.uqReportedInsert.ExecSQL;
apiDB.uqReportedInsert.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqReportedInsert.Connection.InTransaction then
apiDB.uqReportedInsert.Connection.Rollback;
raise;
end;
end;
end;
Result := BuildReportedOptionsResponse(taskId);
end;
function TApiService.RenameReportedName(taskId: string; oldName: string; newName: string): TTaskUserOptionsResponse;
var
oldReportedId: string;
existingReportedId: string;
trimmedOldName: string;
trimmedNewName: string;
begin
trimmedOldName := Trim(oldName);
trimmedNewName := Trim(newName);
if trimmedOldName = '' then
raise Exception.Create('Old reported name cannot be blank.');
if trimmedNewName = '' then
raise Exception.Create('New reported name cannot be blank.');
oldReportedId := FindReportedOptionId(taskId, trimmedOldName);
if oldReportedId = '' then
raise Exception.Create('Reported name not found.');
existingReportedId := FindReportedOptionId(taskId, trimmedNewName);
apiDB.uqReportedRename.Connection.StartTransaction;
try
apiDB.uqRenameTaskItemReportedBy.Close;
apiDB.uqRenameTaskItemReportedBy.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqRenameTaskItemReportedBy.ParamByName('OLD_NAME').AsString := trimmedOldName;
apiDB.uqRenameTaskItemReportedBy.ParamByName('NEW_NAME').AsString := trimmedNewName;
apiDB.uqRenameTaskItemReportedBy.ExecSQL;
if (existingReportedId <> '') and (not SameText(existingReportedId, oldReportedId)) then
begin
apiDB.uqReportedDelete.Close;
apiDB.uqReportedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := oldReportedId;
apiDB.uqReportedDelete.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedDelete.ExecSQL;
end
else
begin
apiDB.uqReportedRename.Close;
apiDB.uqReportedRename.ParamByName('TASK_ITEM_USER_ID').AsString := oldReportedId;
apiDB.uqReportedRename.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedRename.ParamByName('NEW_NAME').AsString := trimmedNewName;
apiDB.uqReportedRename.ExecSQL;
end;
apiDB.uqReportedRename.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqReportedRename.Connection.InTransaction then
apiDB.uqReportedRename.Connection.Rollback;
raise;
end;
end;
Result := BuildReportedOptionsResponse(taskId);
end;
function TApiService.DeleteReportedName(taskId: string; name: string): TTaskUserOptionsResponse;
var
reportedId: string;
reportedName: string;
begin
reportedId := FindReportedOptionId(taskId, name);
reportedName := FindReportedOptionName(taskId, name);
if reportedId = '' then
raise Exception.Create('Reported name not found.');
apiDB.uqReportedDelete.Connection.StartTransaction;
try
apiDB.uqBlankTaskItemReportedBy.Close;
apiDB.uqBlankTaskItemReportedBy.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqBlankTaskItemReportedBy.ParamByName('NAME').AsString := reportedName;
apiDB.uqBlankTaskItemReportedBy.ExecSQL;
apiDB.uqReportedDelete.Close;
apiDB.uqReportedDelete.ParamByName('TASK_ITEM_USER_ID').AsString := reportedId;
apiDB.uqReportedDelete.ParamByName('TASK_ID').AsString := taskId;
apiDB.uqReportedDelete.ExecSQL;
apiDB.uqReportedDelete.Connection.Commit;
except
on E: Exception do
begin
if apiDB.uqReportedDelete.Connection.InTransaction then
apiDB.uqReportedDelete.Connection.Rollback;
raise;
end;
end;
Result := BuildReportedOptionsResponse(taskId);
end;
initialization
RegisterServiceType(TypeInfo(IApiService));
RegisterServiceType(TApiService);
......
[Settings]
MemoLogLevel=4
FileLogLevel=4
webClientVersion=0.8.7
LogFileNum=155
webClientVersion=0.8.8
LogFileNum=160
[Database]
Server=192.168.102.131
......
......@@ -114,10 +114,10 @@
<VerInfo_Locale>1033</VerInfo_Locale>
<DCC_ExeOutput>.\bin</DCC_ExeOutput>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<VerInfo_Keys>CompanyName=EM Systems;FileDescription=$(MSBuildProjectName);FileVersion=0.8.7.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.11;Comments=</VerInfo_Keys>
<VerInfo_Keys>CompanyName=EM Systems;FileDescription=$(MSBuildProjectName);FileVersion=0.8.8.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.11;Comments=</VerInfo_Keys>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>8</VerInfo_MinorVer>
<VerInfo_Release>7</VerInfo_Release>
<VerInfo_Release>8</VerInfo_Release>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment