Commit 0b6d8acf by Mac Stephens

Update Time Entries routing, optional date login, row actions, and copy entry support

parent 074ca098
......@@ -142,7 +142,13 @@ var
end;
begin
if (AUserName = '') or (ALoginType = '') or (ALoginValue = '') or (APassword = '') then
if (AUserName = '') or (ALoginType = '') or (APassword = '') then
begin
AError('Please enter all required login fields.');
Exit;
end;
if SameText(ALoginType, 'task_items') and (ALoginValue = '') then
begin
AError('Please enter all required login fields.');
Exit;
......@@ -166,6 +172,7 @@ procedure TAuthService.Logout;
begin
DeleteToken;
DMConnection.currentTaskId := '';
DMConnection.currentTimeEntryDate := '';
end;
procedure TAuthService.SetToken(AToken: string);
......
......@@ -51,10 +51,11 @@ procedure TFViewLogin.btnLoginClick(Sender: TObject);
var
timeEntriesParam: string;
newUrl: string;
isTimeEntriesMode: Boolean;
procedure LoginSuccess;
begin
if SameText(timeEntriesParam, 'true') then
if isTimeEntriesMode then
begin
DMConnection.currentTaskId := '';
DMConnection.currentTimeEntryDate := edtDate.Text;
......@@ -79,8 +80,11 @@ var
begin
timeEntriesParam := Application.Parameters.Values['time_entries'];
isTimeEntriesMode :=
SameText(timeEntriesParam, 'true') or
(Application.Parameters.Values['task_id'] = '');
if SameText(timeEntriesParam, 'true') then
if isTimeEntriesMode then
begin
AuthService.WebLogin(
edtUsername.Text,
......@@ -149,16 +153,20 @@ end;
procedure TFViewLogin.WebFormCreate(Sender: TObject);
var
timeEntriesParam: string;
isTimeEntriesMode: Boolean;
begin
console.log('TFViewLogin.WebFormCreate FUserId=' + FUserId + ' FTaskId=' + FTaskId + ' FMessage=' + FMessage);
timeEntriesParam := Application.Parameters.Values['time_entries'];
isTimeEntriesMode :=
SameText(timeEntriesParam, 'true') or
(Application.Parameters.Values['task_id'] = '');
edtUsername.Text := FUserId;
edtTaskId.Text := FTaskId;
edtDate.Text := Application.Parameters.Values['date'];
if SameText(timeEntriesParam, 'true') then
if isTimeEntriesMode then
begin
WebLabel1.Caption := 'Time Entries Sign In';
TJSHTMLElement(document.getElementById('view.login.taskid.group')).classList.add('d-none');
......
......@@ -43,12 +43,17 @@ uses
procedure TFViewMain.WebFormCreate(Sender: TObject);
var
userName: string;
isTimeEntriesMode: Boolean;
begin
userName := JS.toString(AuthService.TokenPayload.Properties['user_name']);
lblUsername.Caption := userName;
lblVersion.Caption := 'v' + DMConnection.clientVersion;
if SameText(Application.Parameters.Values['time_entries'], 'true') then
isTimeEntriesMode :=
SameText(Application.Parameters.Values['time_entries'], 'true') or
(Application.Parameters.Values['task_id'] = '');
if isTimeEntriesMode then
begin
lblAppTitle.Caption := 'Time Entries';
ShowForm(TFTimeEntries);
......
......@@ -98,6 +98,20 @@ object FTimeEntries: TFTimeEntries
WidthPercent = 100.000000000000000000
OnClick = btnSearchRangeClick
end
object btnCopyEntry: TWebButton
Left = 440
Top = 122
Width = 96
Height = 25
Caption = 'Copy'
ChildOrder = 7
ElementID = 'btn_copy_entry'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnCopyEntryClick
end
object xdwcTimeEntries: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 460
......
......@@ -24,29 +24,23 @@
Search Range
</button>
<button id="btn_add_entry" class="btn btn-sm btn-success text-nowrap">
Add Entry
</button>
<button id="btn_add_entry" class="btn btn-sm btn-success text-nowrap">
Add Entry
</button>
<button id="btn_delete_entry" class="btn btn-sm btn-danger text-nowrap" disabled>
Delete Entry
</button>
<button id="btn_copy_entry" class="btn btn-sm btn-primary text-nowrap" disabled>
Copy Entry
</button>
<button id="btn_delete_entry" class="btn btn-sm btn-danger text-nowrap" disabled>
Delete Entry
</button>
</div>
</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">
......
......@@ -29,11 +29,13 @@ type
xdwdsTimeEntriesplaceDesc: TStringField;
btnDeleteEntry: TWebButton;
btnSearchRange: TWebButton;
btnCopyEntry: TWebButton;
procedure WebFormCreate(Sender: TObject);
procedure btnAddEntryClick(Sender: TObject);
procedure btnDeleteEntryClick(Sender: TObject);
procedure edtWeekOfExit(Sender: TObject);
[async] procedure btnSearchRangeClick(Sender: TObject);
procedure btnCopyEntryClick(Sender: TObject);
private
FUserId: string;
FUserName: string;
......@@ -95,6 +97,7 @@ type
procedure EditorBlur(Event: TJSEvent);
[async] procedure SaveField(AIndex: Integer; const AFieldName: string);
[async] procedure DeleteSelectedEntry;
function CopySelectedEntry: Boolean;
public
end;
......@@ -134,8 +137,6 @@ begin
document.addEventListener('mousedown', TJSEventHandler(@DocumentMouseDown));
BindNewEntryButtons;
payload := AuthService.TokenPayload;
if Assigned(payload) then
begin
......@@ -148,6 +149,7 @@ begin
SetTimeEntriesLabel(FUserName);
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
anchorDate := DMConnection.currentTimeEntryDate;
......@@ -335,6 +337,7 @@ begin
HideRowValidationMessage;
SetTopControlsEnabled(True);
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
RenderTable;
finally
......@@ -351,10 +354,6 @@ var
html: string;
rowIdx: Integer;
hoursText: string;
actionsEl: TJSHTMLElement;
rowEl: TJSHTMLElement;
rowRect: TJSObject;
topPos: Integer;
function Th(const s: string): string;
begin
......@@ -519,6 +518,21 @@ begin
TdWrap(SummaryTextArea('summary', xdwdsTimeEntriessummary.AsString, rowIdx)) +
'</tr>';
if IsNewEntryRow(rowIdx) then
begin
html := html +
'<tr class="time-new-entry-actions-row">' +
'<td colspan="7" class="p-0 border-0">' +
'<div class="position-sticky d-flex justify-content-end py-1 pe-2" style="right:0; z-index:5; pointer-events:none;">' +
'<div class="bg-body border rounded shadow-sm p-2 d-flex gap-2" style="pointer-events:auto;">' +
'<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>' +
'</td>' +
'</tr>';
end;
xdwdsTimeEntries.Next;
Inc(rowIdx);
end;
......@@ -527,35 +541,7 @@ 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;
BindNewEntryButtons;
BindTableEditors;
EnableAutoGrowTextAreas;
......@@ -664,6 +650,7 @@ begin
HideRowValidationMessage;
SetTopControlsEnabled(True);
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
CaptureTableScroll;
await(LoadTimeEntries);
......@@ -690,6 +677,7 @@ begin
HideRowValidationMessage;
SetTopControlsEnabled(True);
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
RenderTable;
end;
......@@ -760,6 +748,7 @@ begin
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
btnCopyEntry.Enabled := not IsNewEntryRow(idx);
GotoRowIndex(idx);
if xdwdsTimeEntries.Eof then
......@@ -826,6 +815,7 @@ begin
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
......@@ -853,6 +843,7 @@ begin
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
btnCopyEntry.Enabled := not IsNewEntryRow(idx);
ApplyActiveRowState;
end;
......@@ -884,6 +875,7 @@ begin
begin
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := True;
btnCopyEntry.Enabled := True;
ApplyRowValidation(idx);
ShowRowValidationMessage(idx, 'Complete required fields before leaving this row.');
SetTopControlsEnabled(False);
......@@ -934,6 +926,7 @@ begin
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
......@@ -988,6 +981,7 @@ begin
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
btnCopyEntry.Enabled := not IsNewEntryRow(idx);
btn := TJSHTMLElement(document.getElementById(triggerId));
if Assigned(btn) then
......@@ -1076,6 +1070,7 @@ begin
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := True;
btnCopyEntry.Enabled := True;
SetTopControlsEnabled(True);
ApplyActiveRowState;
......@@ -1247,6 +1242,7 @@ begin
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
......@@ -1399,6 +1395,99 @@ begin
SetTopControlsEnabled(False);
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
HideRowValidationMessage;
CaptureTableScroll;
RenderTable;
Result := True;
end;
function TFTimeEntries.CopySelectedEntry: Boolean;
var
taskDate: string;
taskId: string;
taskDisplay: string;
hoursValue: Double;
hasHours: Boolean;
taskTime: string;
place: string;
placeDesc: string;
category: string;
categoryDesc: string;
summary: string;
begin
Result := False;
if FAddingNewEntry then
begin
Utils.ShowErrorModal('Save or cancel the new entry before copying another entry.');
Exit;
end;
if FActiveRowIndex < 0 then
begin
Utils.ShowErrorModal('Select a time entry to copy.');
Exit;
end;
if (FActiveRowIndex >= 0) and (not ValidateRow(FActiveRowIndex)) then
begin
ApplyRowValidation(FActiveRowIndex);
ShowRowValidationMessage(FActiveRowIndex, 'Complete required fields before copying this entry.');
SetTopControlsEnabled(False);
Exit;
end;
GotoRowIndex(FActiveRowIndex);
if xdwdsTimeEntries.Eof then
Exit;
taskDate := xdwdsTimeEntriestaskDate.AsString;
taskId := xdwdsTimeEntriestaskId.AsString;
taskDisplay := xdwdsTimeEntriestaskDisplay.AsString;
hasHours := not xdwdsTimeEntrieshours.IsNull;
if hasHours then
hoursValue := xdwdsTimeEntrieshours.AsFloat
else
hoursValue := 0;
taskTime := xdwdsTimeEntriestaskTime.AsString;
place := xdwdsTimeEntriesplace.AsString;
placeDesc := xdwdsTimeEntriesplaceDesc.AsString;
category := xdwdsTimeEntriescategory.AsString;
categoryDesc := xdwdsTimeEntriescategoryDesc.AsString;
summary := xdwdsTimeEntriessummary.AsString;
xdwdsTimeEntries.Append;
xdwdsTimeEntriesentryId.AsInteger := 0;
xdwdsTimeEntriestaskDate.AsString := taskDate;
xdwdsTimeEntriestaskId.AsString := taskId;
xdwdsTimeEntriestaskDisplay.AsString := taskDisplay;
if hasHours then
xdwdsTimeEntrieshours.AsFloat := hoursValue
else
xdwdsTimeEntrieshours.Clear;
xdwdsTimeEntriestaskTime.AsString := taskTime;
xdwdsTimeEntriesplace.AsString := place;
xdwdsTimeEntriesplaceDesc.AsString := placeDesc;
xdwdsTimeEntriescategory.AsString := category;
xdwdsTimeEntriescategoryDesc.AsString := categoryDesc;
xdwdsTimeEntriessummary.AsString := summary;
xdwdsTimeEntries.Post;
FAddingNewEntry := True;
FNewEntryRowIndex := xdwdsTimeEntries.RecordCount - 1;
FActiveRowIndex := FNewEntryRowIndex;
SetTopControlsEnabled(False);
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
HideRowValidationMessage;
CaptureTableScroll;
......@@ -1408,12 +1497,18 @@ begin
end;
procedure TFTimeEntries.btnAddEntryClick(Sender: TObject);
begin
BeginAddEntry;
end;
procedure TFTimeEntries.btnCopyEntryClick(Sender: TObject);
begin
CopySelectedEntry;
end;
procedure TFTimeEntries.btnDeleteEntryClick(Sender: TObject);
begin
if FActiveRowIndex < 0 then
......@@ -1514,6 +1609,7 @@ begin
FActiveRowIndex := FNewEntryRowIndex;
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
SetTopControlsEnabled(False);
FBlockedRowIndex := FNewEntryRowIndex;
......@@ -1542,6 +1638,7 @@ begin
FActiveRowIndex := idx;
btnDeleteEntry.Enabled := not IsNewEntryRow(idx);
btnCopyEntry.Enabled := not IsNewEntryRow(idx);
if not Assigned(FTaskPickerOffCanvas) then
FTaskPickerOffCanvas := TTaskPickerOffCanvas.Create(xdwcTimeEntries, @TaskPickerTaskSelected);
......@@ -1569,6 +1666,7 @@ begin
FActiveRowIndex := ARowIndex;
btnDeleteEntry.Enabled := not IsNewEntryRow(ARowIndex);
btnCopyEntry.Enabled := not IsNewEntryRow(ARowIndex);
CaptureTableScroll;
RenderTable;
......@@ -1765,6 +1863,7 @@ begin
HideRowValidationMessage;
SetTopControlsEnabled(True);
btnDeleteEntry.Enabled := False;
btnCopyEntry.Enabled := False;
CaptureTableScroll;
await(LoadTimeEntries);
......
......@@ -80,10 +80,14 @@ var
taskIdParam: string;
codeParam: string;
timeEntriesParam: string;
isTimeEntriesMode: Boolean;
begin
timeEntriesParam := Application.Parameters.Values['time_entries'];
isTimeEntriesMode :=
SameText(timeEntriesParam, 'true') or
(Application.Parameters.Values['task_id'] = '');
if SameText(timeEntriesParam, 'true') then
if isTimeEntriesMode then
begin
if AuthService.Authenticated and not AuthService.TokenExpired then
begin
......@@ -120,12 +124,16 @@ var
userIdParam: string;
taskIdParam: string;
timeEntriesParam: string;
isTimeEntriesMode: Boolean;
begin
AuthService.Logout;
timeEntriesParam := Application.Parameters.Values['time_entries'];
isTimeEntriesMode :=
SameText(timeEntriesParam, 'true') or
(Application.Parameters.Values['task_id'] = '');
if SameText(timeEntriesParam, 'true') then
if isTimeEntriesMode then
begin
DisplayLoginView('', '', AMsg);
Exit;
......
......@@ -2,7 +2,7 @@
MemoLogLevel=4
FileLogLevel=4
webClientVersion=0.8.9
LogFileNum=243
LogFileNum=244
[Database]
Server=192.168.102.133
......
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