Commit 892b81cb by Mac Stephens

update add row to save row with save and cancel buttons change check logic for…

update add row to save row with save and cancel buttons change check logic for validation, ver0.9.0.0 created
parent 7a8e3164
......@@ -34,9 +34,20 @@
</div>
</div>
<div id="lbl_validation_message" class="alert alert-danger py-1 px-2 mb-2 d-none small"></div>
<div id="lbl_validation_message" class="alert alert-danger py-1 px-2 mb-2 invisible small" style="min-height: 31px;"></div>
<div id="time_entries_table_host" class="flex-grow-1 min-h-0 overflow-auto"></div>
<div id="time_new_entry_actions" class="position-fixed d-none" style="z-index: 1030;">
<div class="bg-body border rounded shadow-sm p-2 d-flex gap-2">
<button type="button" id="btn_time_new_save" class="btn btn-sm btn-success">
Save
</button>
<button type="button" id="btn_time_new_cancel" class="btn btn-sm btn-outline-secondary">
Cancel
</button>
</div>
</div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="task_picker_offcanvas" aria-labelledby="task_picker_title">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="task_picker_title">Find Task</h5>
......
......@@ -30,7 +30,7 @@ type
btnDeleteEntry: TWebButton;
btnSearchRange: TWebButton;
procedure WebFormCreate(Sender: TObject);
[async] procedure btnAddEntryClick(Sender: TObject);
procedure btnAddEntryClick(Sender: TObject);
procedure btnDeleteEntryClick(Sender: TObject);
procedure edtWeekOfExit(Sender: TObject);
[async] procedure btnSearchRangeClick(Sender: TObject);
......@@ -46,12 +46,23 @@ type
FPendingScrollTop: Integer;
FPendingScrollLeft: Integer;
FActiveRowIndex: Integer;
FBlockedRowIndex: Integer;
FPendingEntryId: string;
FLastMouseDownRowIndex: Integer;
FLastMouseDownTime: Double;
FTaskPickerOffCanvas: TTaskPickerOffCanvas;
FAddingNewEntry: Boolean;
FNewEntryRowIndex: Integer;
[async] procedure LoadTimeEntries;
[async] function AddTimeEntry: Boolean;
function BeginAddEntry: Boolean;
function IsNewEntryRow(AIndex: Integer): Boolean;
procedure ResetNewEntryState;
procedure BindNewEntryButtons;
procedure NewEntrySaveClick(Event: TJSEvent);
procedure NewEntryCancelClick(Event: TJSEvent);
[async] procedure SaveNewEntry;
procedure CancelNewEntry;
procedure RenderTable;
procedure BindTableEditors;
procedure DropdownItemClick(Event: TJSEvent);
......@@ -112,14 +123,19 @@ begin
FPendingScrollTop := 0;
FPendingScrollLeft := 0;
FActiveRowIndex := -1;
FBlockedRowIndex := -1;
FPendingEntryId := '';
FLastMouseDownRowIndex := -1;
FLastMouseDownTime := 0;
FAddingNewEntry := False;
FNewEntryRowIndex := -1;
FTaskPickerOffCanvas := TTaskPickerOffCanvas.Create(xdwcTimeEntries, @TaskPickerTaskSelected);
document.addEventListener('mousedown', TJSEventHandler(@DocumentMouseDown));
BindNewEntryButtons;
payload := AuthService.TokenPayload;
if Assigned(payload) then
begin
......@@ -323,53 +339,6 @@ begin
end;
[async] function TFTimeEntries.AddTimeEntry: Boolean;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
Result := False;
if FUserId = '' then
begin
Utils.ShowErrorModal('Unable to determine logged-in user.');
Exit;
end;
if edtWeekOf.Text = '' then
begin
Utils.ShowErrorModal('Select a week/date before adding an entry.');
Exit;
end;
if (FActiveRowIndex >= 0) and (not ValidateRow(FActiveRowIndex)) then
begin
ApplyRowValidation(FActiveRowIndex);
ShowRowValidationMessage(FActiveRowIndex, 'Complete required fields before adding another entry.');
SetTopControlsEnabled(False);
Exit;
end;
try
response := await(xdwcTimeEntries.RawInvokeAsync(
'ITimeEntryService.AddTimeEntry',
[FUserId, edtWeekOf.Text]
));
resultObj := TJSObject(response.Result);
FPendingEntryId := string(resultObj['value']);
console.log('AddTimeEntry server entry id=' + FPendingEntryId);
Result := True;
except
on E: EXDataClientRequestException do
begin
console.log('AddTimeEntry ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
end;
end;
end;
procedure TFTimeEntries.RenderTable;
......@@ -378,6 +347,10 @@ var
html: string;
rowIdx: Integer;
hoursText: string;
actionsEl: TJSHTMLElement;
rowEl: TJSHTMLElement;
rowRect: TJSObject;
topPos: Integer;
function Th(const s: string): string;
begin
......@@ -550,6 +523,36 @@ begin
SetTotalRowsLabel(rowIdx);
host.innerHTML := html;
actionsEl := TJSHTMLElement(document.getElementById('time_new_entry_actions'));
if Assigned(actionsEl) then
begin
if FAddingNewEntry then
begin
rowEl := TJSHTMLElement(document.querySelector('tr[data-idx="' + IntToStr(FNewEntryRowIndex) + '"]'));
if Assigned(rowEl) then
begin
actionsEl.classList.remove('d-none');
rowRect := TJSObject(rowEl.getBoundingClientRect);
topPos := Round(JS.toNumber(rowRect['bottom']) - 2);
actionsEl.setAttribute(
'style',
'z-index: 1030; ' +
'right: 12px; ' +
'top: ' + IntToStr(topPos) + 'px; ' +
'left: auto; ' +
'bottom: auto;'
);
end
else
actionsEl.classList.add('d-none');
end
else
actionsEl.classList.add('d-none');
end;
BindTableEditors;
EnableAutoGrowTextAreas;
EnableColumnResize;
......@@ -558,6 +561,135 @@ begin
ApplyActiveRowState;
end;
procedure TFTimeEntries.BindNewEntryButtons;
var
btnSave: TJSHTMLElement;
btnCancel: TJSHTMLElement;
begin
btnSave := TJSHTMLElement(document.getElementById('btn_time_new_save'));
if Assigned(btnSave) then
btnSave.addEventListener('click', TJSEventHandler(@NewEntrySaveClick));
btnCancel := TJSHTMLElement(document.getElementById('btn_time_new_cancel'));
if Assigned(btnCancel) then
btnCancel.addEventListener('click', TJSEventHandler(@NewEntryCancelClick));
end;
procedure TFTimeEntries.NewEntrySaveClick(Event: TJSEvent);
begin
Event.preventDefault;
Event.stopPropagation;
SaveNewEntry;
end;
procedure TFTimeEntries.NewEntryCancelClick(Event: TJSEvent);
begin
Event.preventDefault;
Event.stopPropagation;
Utils.ShowConfirmationModal(
'Discard the new unsaved time entry?',
'Discard',
'Cancel',
procedure(AConfirmed: Boolean)
begin
if AConfirmed then
CancelNewEntry;
end
);
end;
[async] procedure TFTimeEntries.SaveNewEntry;
var
response: TXDataClientResponse;
resultObj: TJSObject;
payload: TJSObject;
begin
if not FAddingNewEntry then
Exit;
GotoRowIndex(FNewEntryRowIndex);
if xdwdsTimeEntries.Eof then
Exit;
if not ValidateRow(FNewEntryRowIndex) then
begin
ApplyRowValidation(FNewEntryRowIndex);
ShowRowValidationMessage(FNewEntryRowIndex, 'Complete required fields before saving this entry.');
SetTopControlsEnabled(False);
Exit;
end;
payload := TJSObject.new;
payload['entryId'] := '0';
payload['userId'] := FUserId;
payload['taskDate'] := xdwdsTimeEntriestaskDate.AsString;
payload['taskId'] := xdwdsTimeEntriestaskId.AsString;
payload['hours'] := xdwdsTimeEntrieshours.AsFloat;
payload['taskTime'] := xdwdsTimeEntriestaskTime.AsString;
payload['place'] := xdwdsTimeEntriesplace.AsString;
payload['category'] := xdwdsTimeEntriescategory.AsString;
payload['summary'] := xdwdsTimeEntriessummary.AsString;
Utils.ShowSpinner('spinner');
try
try
response := await(xdwcTimeEntries.RawInvokeAsync(
'ITimeEntryService.SaveTimeEntry',
[payload]
));
resultObj := TJSObject(response.Result);
FPendingEntryId := string(resultObj['value']);
except
on E: EXDataClientRequestException do
begin
console.log('SaveNewEntry ERROR: ' + E.ErrorResult.ErrorMessage);
Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
Exit;
end;
end;
ResetNewEntryState;
FActiveRowIndex := -1;
HideRowValidationMessage;
SetTopControlsEnabled(True);
btnDeleteEntry.Enabled := False;
CaptureTableScroll;
await(LoadTimeEntries);
finally
Utils.HideSpinner('spinner');
end;
end;
procedure TFTimeEntries.CancelNewEntry;
begin
if not FAddingNewEntry then
Exit;
if xdwdsTimeEntries.Active then
begin
GotoRowIndex(FNewEntryRowIndex);
if not xdwdsTimeEntries.Eof then
xdwdsTimeEntries.Delete;
end;
ResetNewEntryState;
FActiveRowIndex := -1;
HideRowValidationMessage;
SetTopControlsEnabled(True);
btnDeleteEntry.Enabled := False;
RenderTable;
end;
procedure TFTimeEntries.BindTableEditors;
var
nodes: TJSNodeList;
......@@ -623,7 +755,7 @@ begin
Exit;
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := True;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
GotoRowIndex(idx);
if xdwdsTimeEntries.Eof then
......@@ -653,6 +785,15 @@ begin
el.setAttribute('data-unsaved-data', '1');
if IsNewEntryRow(idx) then
begin
if ValidateRow(idx) then
ClearRowValidation(idx);
SetTopControlsEnabled(False);
Exit;
end;
if ValidateRow(idx) then
begin
ClearRowValidation(idx);
......@@ -674,6 +815,25 @@ begin
if idx < 0 then
Exit;
if FAddingNewEntry and (idx <> FNewEntryRowIndex) then
begin
Event.preventDefault;
Event.stopPropagation;
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
lblValidationMessage.Caption := 'Save or cancel the new entry before selecting another row.';
lblValidationMessage.ElementHandle.classList.remove('d-none');
lblValidationMessage.ElementHandle.classList.remove('invisible');
ApplyActiveRowState;
Exit;
end;
if (FActiveRowIndex >= 0) and
(idx <> FActiveRowIndex) and
(not ValidateRow(FActiveRowIndex)) then
......@@ -688,7 +848,7 @@ begin
end;
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := True;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
ApplyActiveRowState;
end;
......@@ -707,6 +867,9 @@ begin
if idx < 0 then
Exit;
if IsNewEntryRow(idx) then
Exit;
if (FLastMouseDownRowIndex = idx) and ((TJSDate.now - FLastMouseDownTime) < 500) then
Exit;
......@@ -760,6 +923,39 @@ begin
if (idx < 0) or (fieldName = '') then
Exit;
if FAddingNewEntry and (idx <> FNewEntryRowIndex) then
begin
Event.preventDefault;
Event.stopPropagation;
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
lblValidationMessage.Caption := 'Save or cancel the new entry before changing another row.';
lblValidationMessage.ElementHandle.classList.remove('d-none');
lblValidationMessage.ElementHandle.classList.remove('invisible');
ApplyActiveRowState;
Exit;
end;
if (FActiveRowIndex >= 0) and
(idx <> FActiveRowIndex) and
(not ValidateRow(FActiveRowIndex)) then
begin
Event.preventDefault;
Event.stopPropagation;
ApplyRowValidation(FActiveRowIndex);
ShowRowValidationMessage(FActiveRowIndex, 'Complete required fields before changing another row.');
SetTopControlsEnabled(False);
ApplyActiveRowState;
Exit;
end;
GotoRowIndex(idx);
if xdwdsTimeEntries.Eof then
Exit;
......@@ -787,7 +983,7 @@ begin
xdwdsTimeEntries.Post;
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := True;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
btn := TJSHTMLElement(document.getElementById(triggerId));
if Assigned(btn) then
......@@ -799,8 +995,18 @@ begin
btn.focus;
end;
if not IsNewEntryRow(idx) then
SaveField(idx, fieldName);
if IsNewEntryRow(idx) then
begin
if ValidateRow(idx) then
ClearRowValidation(idx);
SetTopControlsEnabled(False);
Exit;
end;
if ValidateRow(idx) then
begin
ClearRowValidation(idx);
......@@ -866,7 +1072,7 @@ begin
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := True;
SetTopControlsEnabled(False);
SetTopControlsEnabled(True);
ApplyActiveRowState;
firstEditor := TJSHTMLElement(rowEl.querySelector('[data-field="taskId"]'));
......@@ -971,19 +1177,28 @@ var
rowEl: TJSHTMLElement;
firstInvalidEl: TJSHTMLElement;
begin
FBlockedRowIndex := AIndex;
lblValidationMessage.Caption := AMessage;
lblValidationMessage.ElementHandle.classList.remove('d-none');
lblValidationMessage.ElementHandle.classList.remove('invisible');
rowEl := TJSHTMLElement(document.querySelector('tr[data-idx="' + IntToStr(AIndex) + '"]'));
firstInvalidEl := TJSHTMLElement(rowEl.querySelector('.is-invalid'));
firstInvalidEl.focus;
ApplyActiveRowState;
end;
procedure TFTimeEntries.HideRowValidationMessage;
begin
FBlockedRowIndex := -1;
lblValidationMessage.Caption := '';
lblValidationMessage.ElementHandle.classList.add('d-none');
lblValidationMessage.ElementHandle.classList.add('invisible');
ApplyActiveRowState;
end;
......@@ -1019,6 +1234,29 @@ begin
targetEl := TJSHTMLElement(Event.target);
targetRowIndex := GetTargetRowIndex(targetEl);
if FAddingNewEntry and
(targetRowIndex >= 0) and
(targetRowIndex <> FNewEntryRowIndex) then
begin
Event.preventDefault;
Event.stopPropagation;
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
lblValidationMessage.Caption := 'Save or cancel the new entry before selecting another row.';
lblValidationMessage.ElementHandle.classList.remove('d-none');
lblValidationMessage.ElementHandle.classList.remove('invisible');
FLastMouseDownRowIndex := FNewEntryRowIndex;
FLastMouseDownTime := TJSDate.now;
ApplyActiveRowState;
Exit;
end;
if (FActiveRowIndex >= 0) and
(targetRowIndex >= 0) and
(targetRowIndex <> FActiveRowIndex) and
......@@ -1094,19 +1332,81 @@ begin
end;
end;
function TFTimeEntries.IsNewEntryRow(AIndex: Integer): Boolean;
begin
Result := FAddingNewEntry and (AIndex = FNewEntryRowIndex);
end;
[async] procedure TFTimeEntries.btnAddEntryClick(Sender: TObject);
procedure TFTimeEntries.ResetNewEntryState;
begin
Utils.ShowSpinner('spinner');
try
if await(AddTimeEntry) then
FAddingNewEntry := False;
FNewEntryRowIndex := -1;
end;
function TFTimeEntries.BeginAddEntry: Boolean;
begin
Result := False;
if FUserId = '' then
begin
CaptureTableScroll;
await(LoadTimeEntries);
Utils.ShowErrorModal('Unable to determine logged-in user.');
Exit;
end;
finally
Utils.HideSpinner('spinner');
if edtWeekOf.Text = '' then
begin
Utils.ShowErrorModal('Select a week/date before adding an entry.');
Exit;
end;
if FAddingNewEntry then
begin
Utils.ShowErrorModal('Save or cancel the new entry before adding another entry.');
Exit;
end;
if (FActiveRowIndex >= 0) and (not ValidateRow(FActiveRowIndex)) then
begin
ApplyRowValidation(FActiveRowIndex);
ShowRowValidationMessage(FActiveRowIndex, 'Complete required fields before adding another entry.');
SetTopControlsEnabled(False);
Exit;
end;
xdwdsTimeEntries.Append;
xdwdsTimeEntriesentryId.AsInteger := 0;
xdwdsTimeEntriestaskDate.AsString := edtWeekOf.Text;
xdwdsTimeEntriestaskId.AsString := '';
xdwdsTimeEntriestaskDisplay.AsString := '';
xdwdsTimeEntrieshours.Clear;
xdwdsTimeEntriestaskTime.AsString := '';
xdwdsTimeEntriesplace.AsString := '';
xdwdsTimeEntriesplaceDesc.AsString := '';
xdwdsTimeEntriescategory.AsString := '';
xdwdsTimeEntriescategoryDesc.AsString := '';
xdwdsTimeEntriessummary.AsString := '';
xdwdsTimeEntries.Post;
FAddingNewEntry := True;
FNewEntryRowIndex := xdwdsTimeEntries.RecordCount - 1;
FActiveRowIndex := FNewEntryRowIndex;
SetTopControlsEnabled(False);
btnDeleteEntry.Enabled := False;
HideRowValidationMessage;
CaptureTableScroll;
RenderTable;
Result := True;
end;
procedure TFTimeEntries.btnAddEntryClick(Sender: TObject);
begin
BeginAddEntry;
end;
......@@ -1203,8 +1503,41 @@ begin
if idx < 0 then
Exit;
if FAddingNewEntry and (idx <> FNewEntryRowIndex) then
begin
Event.preventDefault;
Event.stopPropagation;
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
lblValidationMessage.Caption := 'Save or cancel the new entry before changing another row.';
lblValidationMessage.ElementHandle.classList.remove('d-none');
lblValidationMessage.ElementHandle.classList.remove('invisible');
ApplyActiveRowState;
Exit;
end;
if (FActiveRowIndex >= 0) and
(idx <> FActiveRowIndex) and
(not ValidateRow(FActiveRowIndex)) then
begin
Event.preventDefault;
Event.stopPropagation;
ApplyRowValidation(FActiveRowIndex);
ShowRowValidationMessage(FActiveRowIndex, 'Complete required fields before changing another row.');
SetTopControlsEnabled(False);
ApplyActiveRowState;
Exit;
end;
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := True;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
if not Assigned(FTaskPickerOffCanvas) then
FTaskPickerOffCanvas := TTaskPickerOffCanvas.Create(xdwcTimeEntries, @TaskPickerTaskSelected);
......@@ -1231,7 +1564,7 @@ begin
xdwdsTimeEntries.Post;
FActiveRowIndex := ARowIndex;
btnDeleteEntry.Enabled := True;
btnDeleteEntry.Enabled := not IsNewEntryRow(ARowIndex);
CaptureTableScroll;
RenderTable;
......@@ -1242,8 +1575,18 @@ begin
if Assigned(btn) then
btn.focus;
if not IsNewEntryRow(ARowIndex) then
SaveField(ARowIndex, 'taskId');
if IsNewEntryRow(ARowIndex) then
begin
if ValidateRow(ARowIndex) then
ClearRowValidation(ARowIndex);
SetTopControlsEnabled(False);
Exit;
end;
if ValidateRow(ARowIndex) then
begin
ClearRowValidation(ARowIndex);
......@@ -1257,14 +1600,18 @@ procedure TFTimeEntries.ApplyActiveRowState;
begin
asm
const activeRowIndex = this.FActiveRowIndex;
const blockedRowIndex = this.FBlockedRowIndex;
document.querySelectorAll('.time-row-selectable').forEach(function(row) {
const rowIndex = parseInt(row.getAttribute('data-idx') || '-1', 10);
if ((activeRowIndex >= 0) && (rowIndex === activeRowIndex))
row.classList.add('table-active');
else
row.classList.remove('table-active');
row.classList.remove('table-danger');
if ((blockedRowIndex >= 0) && (rowIndex === blockedRowIndex))
row.classList.add('table-danger');
else if ((activeRowIndex >= 0) && (rowIndex === activeRowIndex))
row.classList.add('table-active');
});
end;
end;
......@@ -1294,6 +1641,12 @@ begin
if idx < 0 then
Exit;
if IsNewEntryRow(idx) then
begin
console.log('EditorBlur: skip local new row idx=' + IntToStr(idx) + ' field=' + fieldName);
Exit;
end;
console.log('EditorBlur: SAVE idx=' + IntToStr(idx) + ' field=' + fieldName);
SaveField(idx, fieldName);
end;
......
......@@ -94,16 +94,15 @@
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.8.9.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.9.0.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>9</VerInfo_Release>
<TMSURLParams>?time_entries=true&amp;date=2026-05-01</TMSURLParams>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
<TMSURLParams>?time_entries=true&amp;date=2026-05-01</TMSURLParams>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
......
......@@ -7,8 +7,7 @@ object ApiDatabase: TApiDatabase
ProviderName = 'MySQL'
Database = 'eTask'
Username = 'root'
Server = '192.168.102.131'
Connected = True
Server = '192.168.102.133'
LoginPrompt = False
Left = 435
Top = 359
......
......@@ -103,7 +103,7 @@ object AuthDatabase: TAuthDatabase
ProviderName = 'MySQL'
Database = 'eTask'
Username = 'root'
Server = '192.168.102.131'
Server = '192.168.102.133'
LoginPrompt = False
Left = 71
Top = 133
......
......@@ -92,7 +92,7 @@ type
['{B18BCD1E-B19A-4D25-BBA9-50A24FC4C690}']
[HttpGet] function GetTimeEntries(userId, startDate, endDate: string): TTimeEntriesResponse;
[HttpPost] function AddTimeEntry(userId, taskDate: string): string;
[HttpPost] function SaveTimeEntry(Item: TTimeEntrySave): Boolean;
[HttpPost] function SaveTimeEntry(Item: TTimeEntrySave): string;
[HttpPost] function SaveTimeEntryField(Item: TTimeEntryFieldSave): Boolean;
[HttpPost] function DeleteTimeEntry(userId: string; entryId: Integer): Boolean;
function GetTaskPickerCustomers(userId: string): TTaskPickerOptionsResponse;
......
......@@ -32,7 +32,7 @@ type
public
function GetTimeEntries(userId, startDate, endDate: string): TTimeEntriesResponse;
function AddTimeEntry(userId, taskDate: string): string;
function SaveTimeEntry(Item: TTimeEntrySave): Boolean;
function SaveTimeEntry(Item: TTimeEntrySave): string;
function SaveTimeEntryField(Item: TTimeEntryFieldSave): Boolean;
function GetTaskPickerCustomers(userId: string): TTaskPickerOptionsResponse;
function GetTaskPickerProjects(userId, customerId: string): TTaskPickerOptionsResponse;
......@@ -476,13 +476,38 @@ begin
end;
function TTimeEntryService.SaveTimeEntry(Item: TTimeEntrySave): Boolean;
function TTimeEntryService.SaveTimeEntry(Item: TTimeEntrySave): string;
var
taskDateValue: TDateTime;
entryId: string;
isNewEntry: Boolean;
q: TUniQuery;
begin
Logger.Log(4, Format('TimeEntryService.SaveTimeEntry - ENTRY_ID="%s" USER_ID="%s"', [Item.entryId, Item.userId]));
if not Assigned(Item) then
raise Exception.Create('SaveTimeEntry: Item is nil.');
if Trim(Item.userId) = '' then
raise Exception.Create('SaveTimeEntry: Invalid userId.');
if Trim(Item.taskDate) = '' then
raise Exception.Create('SaveTimeEntry: Task date is required.');
if Trim(Item.taskId) = '' then
raise Exception.Create('SaveTimeEntry: Task is required.');
if Item.hours <= 0 then
raise Exception.Create('SaveTimeEntry: Hours must be greater than zero.');
if Trim(Item.category) = '' then
raise Exception.Create('SaveTimeEntry: Category is required.');
if Trim(Item.summary) = '' then
raise Exception.Create('SaveTimeEntry: Summary is required.');
Logger.Log(4, Format(
'TimeEntryService.SaveTimeEntry - ENTRY_ID="%s" USER_ID="%s"',
[Item.entryId, Item.userId]
));
taskDateValue := ParseIsoDate(Item.taskDate);
isNewEntry := StrToIntDef(Item.entryId, 0) <= 0;
......@@ -491,15 +516,70 @@ begin
begin
entryId := GetNextIdValue('TimeEntryId');
apiDB.uqAddTimeEntry.Close;
apiDB.uqAddTimeEntry.ParamByName('ENTRY_ID').AsString := entryId;
apiDB.uqAddTimeEntry.ParamByName('USER_ID').AsString := Item.userId;
apiDB.uqAddTimeEntry.ParamByName('TASK_DATE').AsDateTime := taskDateValue;
apiDB.uqAddTimeEntry.ParamByName('CREATED_BY').AsString := Item.userId;
apiDB.uqAddTimeEntry.ParamByName('MODIFIED_BY').AsString := Item.userId;
apiDB.uqAddTimeEntry.ExecSQL;
q := TUniQuery.Create(nil);
try
q.Connection := apiDB.ucETaskApi;
q.SQL.Text :=
'insert into time_items ( ' +
' ENTRY_ID, ' +
' USER_ID, ' +
' TASK_DATE, ' +
' TASK_ID, ' +
' PROJECT_ID, ' +
' HOURS, ' +
' TASK_TIME, ' +
' PLACE, ' +
' CATEGORY, ' +
' SUMMARY, ' +
' CREATE_DATE, ' +
' CREATED_BY, ' +
' MODIFY_DATE, ' +
' MODIFIED_BY ' +
') values ( ' +
' :ENTRY_ID, ' +
' :USER_ID, ' +
' :TASK_DATE, ' +
' :TASK_ID, ' +
' (select t.PROJECT_ID from tasks t where t.TASK_ID = :TASK_ID), ' +
' :HOURS, ' +
' :TASK_TIME, ' +
' :PLACE, ' +
' :CATEGORY, ' +
' :SUMMARY, ' +
' now(), ' +
' :CREATED_BY, ' +
' now(), ' +
' :MODIFIED_BY ' +
')';
q.ParamByName('ENTRY_ID').AsString := entryId;
q.ParamByName('USER_ID').AsString := Item.userId;
q.ParamByName('TASK_DATE').AsDateTime := taskDateValue;
q.ParamByName('TASK_ID').AsString := Item.taskId;
q.ParamByName('HOURS').AsFloat := Item.hours;
if Trim(Item.taskTime) = '' then
q.ParamByName('TASK_TIME').Clear
else
q.ParamByName('TASK_TIME').AsString := Item.taskTime;
if Trim(Item.place) = '' then
q.ParamByName('PLACE').Clear
else
q.ParamByName('PLACE').AsString := Item.place;
q.ParamByName('CATEGORY').AsString := Item.category;
q.ParamByName('SUMMARY').AsString := Item.summary;
q.ParamByName('CREATED_BY').AsString := Item.userId;
q.ParamByName('MODIFIED_BY').AsString := Item.userId;
q.ExecSQL;
finally
q.Free;
end;
end
else
begin
entryId := Item.entryId;
apiDB.uqSaveTimeEntry.Close;
......@@ -524,10 +604,11 @@ begin
apiDB.uqSaveTimeEntry.ParamByName('MODIFIED_BY').AsString := Item.userId;
apiDB.uqSaveTimeEntry.ExecSQL;
end;
Result := True;
Result := entryId;
Logger.Log(4, 'TimeEntryService.SaveTimeEntry - saved ENTRY_ID=' + entryId);
Logger.Log(4, 'TimeEntryService.SaveTimeEntry - saved ENTRY_ID=' + Result);
end;
......
......@@ -2,10 +2,10 @@
MemoLogLevel=4
FileLogLevel=4
webClientVersion=0.8.9
LogFileNum=236
LogFileNum=242
[Database]
Server=192.168.102.131
Server=192.168.102.133
--Server=192.168.116.131
--Server=192.168.159.10
Database=eTask
......
......@@ -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.9.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.9.0.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>9</VerInfo_Release>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_AutoIncVersion>true</VerInfo_AutoIncVersion>
</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