Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
emT3web
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mac Stephens
emT3web
Commits
e56dc151
Commit
e56dc151
authored
Nov 09, 2026
by
Mac Stephens
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev'
parents
21727feb
236fcdca
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1075 additions
and
135 deletions
+1075
-135
.gitignore
.gitignore
+1
-0
ConnectionModule.pas
emT3Web/ConnectionModule.pas
+1
-1
View.TaskItems.dfm
emT3Web/View.TaskItems.dfm
+13
-0
View.TaskItems.html
emT3Web/View.TaskItems.html
+1
-0
View.TaskItems.pas
emT3Web/View.TaskItems.pas
+54
-0
View.TimeEntries.dfm
emT3Web/View.TimeEntries.dfm
+4
-4
View.TimeEntries.html
emT3Web/View.TimeEntries.html
+16
-3
View.TimeEntries.pas
emT3Web/View.TimeEntries.pas
+251
-12
time-entries.css
emT3Web/css/time-entries.css
+15
-0
emT3web.dproj
emT3Web/emT3web.dproj
+3
-3
uTaskPickerOffCanvas.pas
emT3Web/uTaskPickerOffCanvas.pas
+374
-34
Api.Database.dfm
emT3XDataServer/Source/Api.Database.dfm
+97
-46
Api.Database.pas
emT3XDataServer/Source/Api.Database.pas
+5
-3
TaskItem.Service.pas
emT3XDataServer/Source/TaskItem.Service.pas
+1
-0
TaskItem.ServiceImpl.pas
emT3XDataServer/Source/TaskItem.ServiceImpl.pas
+62
-0
TimeEntry.Service.pas
emT3XDataServer/Source/TimeEntry.Service.pas
+14
-4
TimeEntry.ServiceImpl.pas
emT3XDataServer/Source/TimeEntry.ServiceImpl.pas
+153
-16
emT3XDataServer.ini
emT3XDataServer/bin/emT3XDataServer.ini
+8
-6
emT3XDataServer.dproj
emT3XDataServer/emT3XDataServer.dproj
+2
-3
No files found.
.gitignore
View file @
e56dc151
...
...
@@ -13,6 +13,7 @@ emT3XDataServer/bin/static/
emT3XDataServer/Source/__history/
emT3XDataServer/Source/__recovery/
emT3XDataServer/Win32/
.validation/
*.exe
*.local
...
...
emT3Web/ConnectionModule.pas
View file @
e56dc151
...
...
@@ -22,7 +22,7 @@ type
public
currentTaskId
:
string
;
currentTimeEntryDate
:
string
;
const
clientVersion
=
'0.9.
3
'
;
const
clientVersion
=
'0.9.
4.0
'
;
procedure
InitApp
(
SuccessProc
:
TSuccessProc
;
UnauthorizedAccessProc
:
TUnauthorizedAccessProc
);
procedure
SetClientConfig
(
Callback
:
TVersionCheckCallback
);
...
...
emT3Web/View.TaskItems.dfm
View file @
e56dc151
...
...
@@ -58,6 +58,19 @@ object FTaskItems: TFTaskItems
WidthPercent = 100.000000000000000000
OnClick = btnInsertRowClick
end
object btnCopyRow: TWebButton
Left = 78
Top = 210
Width = 96
Height = 25
Caption = 'Copy Row'
Enabled = False
ElementID = 'btn_copy_row'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnCopyRowClick
end
object btnTextColor: TWebButton
Left = 282
Top = 197
...
...
emT3Web/View.TaskItems.html
View file @
e56dc151
...
...
@@ -27,6 +27,7 @@
<button
id=
"btn_add_row"
class=
"btn btn-sm btn-success text-nowrap"
>
Add Row
</button>
<button
id=
"btn_insert_row"
class=
"btn btn-sm btn-success text-nowrap"
>
Insert Row
</button>
<button
id=
"btn_copy_row"
class=
"btn btn-sm btn-primary text-nowrap"
disabled
>
Copy Row
</button>
<button
id=
"btn_delete_row"
class=
"btn btn-sm btn-danger text-nowrap"
>
Delete Row
</button>
<button
id=
"btn_reload"
class=
"btn btn-sm btn-primary text-nowrap"
>
Reload
</button>
</div>
...
...
emT3Web/View.TaskItems.pas
View file @
e56dc151
...
...
@@ -31,6 +31,7 @@ type
xdwdsTaskstaskItemId
:
TIntegerField
;
btnDeleteRow
:
TWebButton
;
btnInsertRow
:
TWebButton
;
btnCopyRow
:
TWebButton
;
btnTextColor
:
TWebButton
;
btnFmtBold
:
TWebButton
;
btnFmtItalic
:
TWebButton
;
...
...
@@ -40,6 +41,7 @@ type
procedure
WebFormCreate
(
Sender
:
TObject
);
[
async
]
procedure
btnDeleteRowClick
(
Sender
:
TObject
);
[
async
]
procedure
btnInsertRowClick
(
Sender
:
TObject
);
[
async
]
procedure
btnCopyRowClick
(
Sender
:
TObject
);
procedure
btnTextColorClick
(
Sender
:
TObject
);
procedure
btnFmtBoldClick
(
Sender
:
TObject
);
procedure
btnFmtItalicClick
(
Sender
:
TObject
);
...
...
@@ -61,6 +63,8 @@ type
FActiveRichEditorId
:
string
;
FLastSelectionRange
:
JSValue
;
FNameManager
:
TNameManager
;
FCopyingRow
:
Boolean
;
[
async
]
function
CopyTaskRow
:
Boolean
;
[
async
]
procedure
LoadTasks
(
const
ATaskId
:
string
);
procedure
RenderTable
;
procedure
BindTableEditors
;
...
...
@@ -134,6 +138,8 @@ begin
FStatusOptions
:=
TJSArray
.
new
;
FSelectedTaskItemId
:=
0
;
FSelectedTaskId
:=
0
;
FCopyingRow
:=
False
;
btnCopyRow
.
Enabled
:=
False
;
FNameManager
:=
TNameManager
.
Create
(
function
(
const
AFieldName
:
string
):
TJSArray
...
...
@@ -465,6 +471,53 @@ begin
end
;
end
;
[
async
]
procedure
TFTaskItems
.
btnCopyRowClick
(
Sender
:
TObject
);
begin
if
FCopyingRow
or
(
FSelectedTaskItemId
<=
0
)
then
Exit
;
FCopyingRow
:=
True
;
btnCopyRow
.
Enabled
:=
False
;
Utils
.
ShowSpinner
(
'spinner'
);
try
if
await
(
CopyTaskRow
)
then
begin
CaptureTableScroll
;
await
(
LoadTasks
(
FTaskId
));
end
;
finally
FCopyingRow
:=
False
;
btnCopyRow
.
Enabled
:=
FSelectedTaskItemId
>
0
;
Utils
.
HideSpinner
(
'spinner'
);
end
;
end
;
[
async
]
function
TFTaskItems
.
CopyTaskRow
:
Boolean
;
var
response
:
TXDataClientResponse
;
sourceTaskItemId
:
Integer
;
begin
Result
:=
False
;
sourceTaskItemId
:=
FSelectedTaskItemId
;
try
response
:=
await
(
xdwcTasks
.
RawInvokeAsync
(
'ITaskItemService.CopyTaskRow'
,
[
FTaskId
,
sourceTaskItemId
]));
console
.
log
(
'CopyTaskRow: response='
+
string
(
TJSJSON
.
stringify
(
response
.
Result
)));
FSelectedTaskItemId
:=
StrToIntDef
(
string
(
TJSObject
(
response
.
Result
)[
'value'
]),
0
);
FPendingFocusTaskItemId
:=
FSelectedTaskItemId
;
FPendingFocusField
:=
'application'
;
Result
:=
True
;
except
on
E
:
EXDataClientRequestException
do
begin
console
.
log
(
'CopyTaskRow ERROR: '
+
E
.
ErrorResult
.
ErrorMessage
);
Utils
.
ShowErrorModal
(
E
.
ErrorResult
.
ErrorMessage
);
end
;
end
;
end
;
procedure
TFTaskItems
.
RowClick
(
Event
:
TJSEvent
);
var
rowEl
:
TJSHTMLElement
;
...
...
@@ -488,6 +541,7 @@ end;
procedure
TFTaskItems
.
ApplySelectedRowState
;
begin
btnCopyRow
.
Enabled
:=
(
FSelectedTaskItemId
>
0
)
and
not
FCopyingRow
;
asm
const
selectedTaskItemId
=
this
.
FSelectedTaskItemId
;
document
.
querySelectorAll
(
'.task-row-selectable'
).
forEach
(
function
(
row
)
{
...
...
emT3Web/View.TimeEntries.dfm
View file @
e56dc151
...
...
@@ -99,10 +99,10 @@ object FTimeEntries: TFTimeEntries
OnClick = btnSearchRangeClick
end
object btnCopyEntry: TWebButton
Left =
440
Top = 1
2
2
Width =
96
Height =
25
Left =
88
Top = 1
5
2
Width =
79
Height =
31
Caption = 'Copy'
ChildOrder = 7
ElementID = 'btn_copy_entry'
...
...
emT3Web/View.TimeEntries.html
View file @
e56dc151
...
...
@@ -3,6 +3,7 @@
<h5
class=
"mb-0 text-nowrap"
id=
"lbl_time_entries_title"
></h5>
<div
class=
"d-flex align-items-center gap-2 ms-auto flex-nowrap"
>
<div
id=
"lbl_time_total_hours"
class=
"me-2 text-nowrap fw-semibold"
>
Total Hours: 0
</div>
<div
id=
"lbl_time_total_rows"
class=
"me-2 text-nowrap"
></div>
<div
class=
"d-flex align-items-center gap-2 flex-nowrap"
>
...
...
@@ -72,12 +73,24 @@
<div
id=
"task_picker_status"
class=
"alert alert-danger py-1 px-2 mb-3 d-none small"
></div>
<div
class=
"d-flex justify-content-end gap-2"
>
<div
class=
"d-flex align-items-center gap-2"
>
<div
class=
"d-flex align-items-center gap-2 me-auto"
>
<input
class=
"form-check-input m-0"
type=
"checkbox"
id=
"task_picker_add_focus"
/>
<label
class=
"form-check-label mb-0"
for=
"task_picker_add_focus"
>
Focused Task
</label>
</div>
<button
type=
"button"
class=
"btn btn-outline-secondary btn-sm"
data-bs-dismiss=
"offcanvas"
>
Cancel
</button>
<button
type=
"button"
class=
"btn btn-primary btn-sm"
id=
"btn_task_picker_save"
disabled
>
Save
<button
type=
"button"
class=
"btn btn-outline-success btn-sm"
id=
"btn_task_picker_save"
disabled
>
Save
</button>
<button
type=
"button"
class=
"btn btn-primary btn-sm"
id=
"btn_task_picker_select"
disabled
>
Select
</button>
</div>
</div>
...
...
emT3Web/View.TimeEntries.pas
View file @
e56dc151
...
...
@@ -55,6 +55,8 @@ type
FTaskPickerOffCanvas
:
TTaskPickerOffCanvas
;
FAddingNewEntry
:
Boolean
;
FNewEntryRowIndex
:
Integer
;
FEditingEntryId
:
Integer
;
FOriginalTaskDate
:
string
;
[
async
]
procedure
LoadTimeEntries
;
function
BeginAddEntry
:
Boolean
;
...
...
@@ -63,6 +65,8 @@ type
procedure
BindNewEntryButtons
;
procedure
NewEntrySaveClick
(
Event
:
TJSEvent
);
procedure
NewEntryCancelClick
(
Event
:
TJSEvent
);
[
async
]
procedure
EditedDateSaveClick
(
Event
:
TJSEvent
);
procedure
EditedDateCancelClick
(
Event
:
TJSEvent
);
[
async
]
procedure
SaveNewEntry
;
procedure
CancelNewEntry
;
procedure
RenderTable
;
...
...
@@ -76,6 +80,7 @@ type
function
DateToIso
(
const
AValue
:
TDateTime
):
string
;
procedure
SetWeekRangeFromAnchor
(
const
ADateValue
:
string
;
ALoadAfterSet
:
Boolean
);
procedure
SetTotalRowsLabel
(
ARowCount
:
Integer
);
procedure
UpdateTotalHours
;
procedure
SetTimeEntriesLabel
(
const
AName
:
string
);
procedure
CaptureTableScroll
;
procedure
RestoreTableScroll
;
...
...
@@ -94,9 +99,10 @@ type
procedure
DocumentMouseDown
(
Event
:
TJSEvent
);
[
async
]
procedure
TaskPickerButtonClick
(
Event
:
TJSEvent
);
procedure
TaskPickerTaskSelected
(
ARowIndex
:
Integer
;
const
ATaskId
,
ATaskDisplay
:
string
);
procedure
TaskPickerFocusChanged
(
const
ATaskId
,
ATaskDisplay
:
string
;
AFocused
:
Boolean
);
procedure
ApplyActiveRowState
;
procedure
EditorBlur
(
Event
:
TJSEvent
);
[
async
]
procedure
SaveField
(
AIndex
:
Integer
;
const
AFieldName
:
string
)
;
[
async
]
function
SaveField
(
AIndex
:
Integer
;
const
AFieldName
:
string
):
Boolean
;
[
async
]
procedure
DeleteSelectedEntry
;
function
CopySelectedEntry
:
Boolean
;
public
...
...
@@ -134,7 +140,7 @@ begin
FAddingNewEntry
:=
False
;
FNewEntryRowIndex
:=
-
1
;
FTaskPickerOffCanvas
:=
TTaskPickerOffCanvas
.
Create
(
xdwcTimeEntries
,
@
TaskPickerTaskSelected
);
FTaskPickerOffCanvas
:=
TTaskPickerOffCanvas
.
Create
(
xdwcTimeEntries
,
@
TaskPickerTaskSelected
,
@
TaskPickerFocusChanged
);
document
.
addEventListener
(
'mousedown'
,
TJSEventHandler
(@
DocumentMouseDown
));
...
...
@@ -215,7 +221,7 @@ var
weekEnd
:
TDateTime
;
begin
anchorDate
:=
IsoToDate
(
ADateValue
);
weekStart
:=
anchorDate
-
(
DayOfTheWeek
(
anchorDate
)
-
1
)
;
weekStart
:=
anchorDate
;
weekEnd
:=
weekStart
+
6
;
FUpdatingDates
:=
True
;
...
...
@@ -271,6 +277,37 @@ begin
el
.
innerText
:=
'Total Rows: '
+
IntToStr
(
ARowCount
);
end
;
procedure
TFTimeEntries
.
UpdateTotalHours
;
var
el
:
TJSHTMLElement
;
totalHours
:
Double
;
currentRecNo
:
Integer
;
begin
totalHours
:=
0
;
if
xdwdsTimeEntries
.
Active
then
begin
currentRecNo
:=
xdwdsTimeEntries
.
RecNo
;
xdwdsTimeEntries
.
First
;
while
not
xdwdsTimeEntries
.
Eof
do
begin
if
(
xdwdsTimeEntriesentryId
.
AsInteger
>
0
)
and
(
xdwdsTimeEntriestaskDate
.
AsString
>=
FStartDate
)
and
(
xdwdsTimeEntriestaskDate
.
AsString
<=
FEndDate
)
and
(
not
xdwdsTimeEntrieshours
.
IsNull
)
then
totalHours
:=
totalHours
+
xdwdsTimeEntrieshours
.
AsFloat
;
xdwdsTimeEntries
.
Next
;
end
;
if
currentRecNo
>
0
then
xdwdsTimeEntries
.
RecNo
:=
currentRecNo
;
end
;
el
:=
TJSHTMLElement
(
document
.
getElementById
(
'lbl_time_total_hours'
));
el
.
innerText
:=
'Total Hours: '
+
FormatFloat
(
'0.##'
,
totalHours
);
end
;
procedure
TFTimeEntries
.
SetTimeEntriesLabel
(
const
AName
:
string
);
var
el
:
TJSHTMLElement
;
...
...
@@ -284,6 +321,7 @@ begin
el
.
innerText
:=
displayName
+
'''s Time Entries'
;
end
;
[
async
]
procedure
TFTimeEntries
.
LoadTimeEntries
;
var
response
:
TXDataClientResponse
;
...
...
@@ -333,6 +371,7 @@ begin
xdwdsTimeEntries
.
Close
;
xdwdsTimeEntries
.
SetJsonData
(
itemsArray
);
xdwdsTimeEntries
.
Open
;
UpdateTotalHours
;
FActiveRowIndex
:=
-
1
;
HideRowValidationMessage
;
...
...
@@ -347,8 +386,6 @@ begin
end
;
procedure
TFTimeEntries
.
RenderTable
;
var
host
:
TJSHTMLElement
;
...
...
@@ -459,8 +496,8 @@ var
if
AShowTaskPicker
then
begin
Result
:=
Result
+
'<button type="button" class="btn btn-outline-secondary time-task-picker-btn flex-shrink-0 text-nowrap" '
+
'data-idx="'
+
IntToStr
(
AIdx
)
+
'" title="
Find task">Find
</button>'
+
'<button type="button" class="btn btn-outline-secondary time-task-picker-btn flex-shrink-0 text-nowrap
ms-0
" '
+
'data-idx="'
+
IntToStr
(
AIdx
)
+
'" title="
Select a task">Task
</button>'
+
'</div>'
;
end
;
end
;
...
...
@@ -532,7 +569,15 @@ begin
'</div>'
+
'</td>'
+
'</tr>'
;
end
;
end
else
html
:=
html
+
'<tr id="time-edit-actions-'
+
IntToStr
(
rowIdx
)
+
'" hidden>'
+
'<td colspan="7">'
+
'<button type="button" class="btn btn-sm btn-success time-edit-save" data-idx="'
+
IntToStr
(
rowIdx
)
+
'">Save</button> '
+
'<button type="button" class="btn btn-sm btn-outline-secondary time-edit-cancel" data-idx="'
+
IntToStr
(
rowIdx
)
+
'">Cancel</button>'
+
'</td>'
+
'</tr>'
;
xdwdsTimeEntries
.
Next
;
Inc
(
rowIdx
);
...
...
@@ -683,6 +728,98 @@ begin
RenderTable
;
end
;
[
async
]
procedure
TFTimeEntries
.
EditedDateSaveClick
(
Event
:
TJSEvent
);
var
saveButton
,
cancelButton
:
TJSHTMLButtonElement
;
dateInput
:
TJSHTMLInputElement
;
rowEl
:
TJSHTMLElement
;
idx
,
savedEntryId
:
Integer
;
rowSelector
:
string
;
begin
saveButton
:=
TJSHTMLButtonElement
(
Event
.
target
);
idx
:=
StrToIntDef
(
string
(
saveButton
.
getAttribute
(
'data-idx'
)),
-
1
);
if
(
idx
<
0
)
or
(
FEditingEntryId
=
0
)
or
(
not
xdwdsTimeEntries
.
Active
)
then
Exit
;
GotoRowIndex
(
idx
);
if
xdwdsTimeEntries
.
Eof
then
Exit
;
savedEntryId
:=
xdwdsTimeEntriesentryId
.
AsInteger
;
if
savedEntryId
<>
FEditingEntryId
then
Exit
;
if
Trim
(
xdwdsTimeEntriestaskDate
.
AsString
)
=
''
then
begin
Utils
.
ShowErrorModal
(
'Enter a date before saving.'
);
Exit
;
end
;
rowSelector
:=
'.time-row-selectable[data-entry-id="'
+
IntToStr
(
savedEntryId
)
+
'"]'
;
rowEl
:=
TJSHTMLElement
(
document
.
querySelector
(
rowSelector
));
dateInput
:=
TJSHTMLInputElement
(
rowEl
.
querySelector
(
'[data-field="taskDate"]'
));
cancelButton
:=
TJSHTMLButtonElement
(
document
.
querySelector
(
'.time-edit-cancel[data-idx="'
+
IntToStr
(
idx
)
+
'"]'
));
saveButton
.
disabled
:=
True
;
cancelButton
.
disabled
:=
True
;
dateInput
.
disabled
:=
True
;
try
if
not
await
(
SaveField
(
idx
,
'taskDate'
))
then
Exit
;
finally
saveButton
.
disabled
:=
False
;
cancelButton
.
disabled
:=
False
;
dateInput
.
disabled
:=
False
;
end
;
FEditingEntryId
:=
0
;
FOriginalTaskDate
:=
''
;
CaptureTableScroll
;
RenderTable
;
rowEl
:=
TJSHTMLElement
(
document
.
querySelector
(
rowSelector
));
if
Assigned
(
rowEl
)
then
rowEl
.
classList
.
add
(
'time-row-saved'
);
end
;
procedure
TFTimeEntries
.
EditedDateCancelClick
(
Event
:
TJSEvent
);
var
el
:
TJSHTMLElement
;
idx
:
Integer
;
begin
Event
.
preventDefault
;
Event
.
stopPropagation
;
el
:=
TJSHTMLElement
(
Event
.
target
);
idx
:=
StrToIntDef
(
string
(
el
.
getAttribute
(
'data-idx'
)),
-
1
);
if
(
idx
<
0
)
or
(
FEditingEntryId
=
0
)
or
(
not
xdwdsTimeEntries
.
Active
)
then
Exit
;
GotoRowIndex
(
idx
);
if
xdwdsTimeEntries
.
Eof
then
Exit
;
if
xdwdsTimeEntriesentryId
.
AsInteger
<>
FEditingEntryId
then
Exit
;
xdwdsTimeEntries
.
Edit
;
xdwdsTimeEntriestaskDate
.
AsString
:=
FOriginalTaskDate
;
xdwdsTimeEntries
.
Post
;
FEditingEntryId
:=
0
;
FOriginalTaskDate
:=
''
;
CaptureTableScroll
;
RenderTable
;
if
ValidateRow
(
idx
)
then
ClearRowValidation
(
idx
);
end
;
procedure
TFTimeEntries
.
BindTableEditors
;
var
nodes
:
TJSNodeList
;
...
...
@@ -700,6 +837,22 @@ begin
el
.
addEventListener
(
'blur'
,
TJSEventHandler
(@
EditorBlur
));
end
;
nodes
:=
document
.
querySelectorAll
(
'.time-edit-cancel'
);
console
.
log
(
'BindTableEditors: edit row cancel button clicked'
);
for
i
:=
0
to
nodes
.
length
-
1
do
begin
el
:=
TJSHTMLElement
(
nodes
.
item
(
i
));
el
.
addEventListener
(
'click'
,
TJSEventHandler
(@
EditedDateCancelClick
));
end
;
nodes
:=
document
.
querySelectorAll
(
'.time-edit-save'
);
console
.
log
(
'BindTableEditors: edit row save button click'
);
for
i
:=
0
to
nodes
.
length
-
1
do
begin
el
:=
TJSHTMLElement
(
nodes
.
item
(
i
));
el
.
addEventListener
(
'click'
,
TJSEventHandler
(@
EditedDateSaveClick
));
end
;
nodes
:=
document
.
querySelectorAll
(
'.time-dd-item'
);
console
.
log
(
'BindTableEditors: time-dd-item count='
+
IntToStr
(
nodes
.
length
));
for
i
:=
0
to
nodes
.
length
-
1
do
...
...
@@ -734,6 +887,7 @@ var
idxStr
:
string
;
fieldName
:
string
;
newVal
:
string
;
actionsRow
:
TJSHTMLElement
;
begin
if
not
xdwdsTimeEntries
.
Active
then
Exit
;
...
...
@@ -755,6 +909,14 @@ begin
if
xdwdsTimeEntries
.
Eof
then
Exit
;
if
SameText
(
fieldName
,
'taskDate'
)
and
(
xdwdsTimeEntriesentryId
.
AsInteger
>
0
)
and
(
FEditingEntryId
=
0
)
then
begin
FEditingEntryId
:=
xdwdsTimeEntriesentryId
.
AsInteger
;
FOriginalTaskDate
:=
xdwdsTimeEntriestaskDate
.
AsString
;
end
;
newVal
:=
string
(
TJSObject
(
el
)[
'value'
]);
xdwdsTimeEntries
.
Edit
;
...
...
@@ -777,6 +939,15 @@ begin
xdwdsTimeEntries
.
Post
;
if
SameText
(
fieldName
,
'taskDate'
)
and
(
FEditingEntryId
>
0
)
and
(
xdwdsTimeEntriesentryId
.
AsInteger
=
FEditingEntryId
)
then
begin
actionsRow
:=
TJSHTMLElement
(
document
.
getElementById
(
'time-edit-actions-'
+
IntToStr
(
idx
)));
if
Assigned
(
actionsRow
)
then
actionsRow
.
removeAttribute
(
'hidden'
);
end
;
el
.
setAttribute
(
'data-unsaved-data'
,
'1'
);
if
IsNewEntryRow
(
idx
)
then
...
...
@@ -1616,6 +1787,7 @@ end;
var
el
:
TJSHTMLElement
;
idx
:
Integer
;
taskId
:
string
;
begin
Event
.
preventDefault
;
Event
.
stopPropagation
;
...
...
@@ -1665,9 +1837,63 @@ begin
btnCopyEntry
.
Enabled
:=
not
IsNewEntryRow
(
idx
);
if
not
Assigned
(
FTaskPickerOffCanvas
)
then
FTaskPickerOffCanvas
:=
TTaskPickerOffCanvas
.
Create
(
xdwcTimeEntries
,
@
TaskPickerTaskSelected
);
FTaskPickerOffCanvas
:=
TTaskPickerOffCanvas
.
Create
(
xdwcTimeEntries
,
@
TaskPickerTaskSelected
,
@
TaskPickerFocusChanged
);
// Pass the row's current task so the picker opens on it (blank for rows without one)
taskId
:=
''
;
GotoRowIndex
(
idx
);
if
not
xdwdsTimeEntries
.
Eof
then
taskId
:=
xdwdsTimeEntriestaskId
.
AsString
;
await
(
FTaskPickerOffCanvas
.
Open
(
FUserId
,
idx
));
await
(
FTaskPickerOffCanvas
.
Open
(
FUserId
,
idx
,
FTaskOptions
,
taskId
));
end
;
procedure
TFTimeEntries
.
TaskPickerFocusChanged
(
const
ATaskId
,
ATaskDisplay
:
string
;
AFocused
:
Boolean
);
var
i
:
Integer
;
taskIndex
:
Integer
;
taskOption
:
TJSObject
;
begin
if
not
Assigned
(
FTaskOptions
)
then
FTaskOptions
:=
TJSArray
.
new
;
taskIndex
:=
-
1
;
for
i
:=
0
to
FTaskOptions
.
length
-
1
do
begin
taskOption
:=
TJSObject
(
FTaskOptions
[
i
]);
if
string
(
taskOption
[
'taskId'
])
=
ATaskId
then
begin
taskIndex
:=
i
;
Break
;
end
;
end
;
if
AFocused
then
begin
if
taskIndex
>=
0
then
Exit
;
taskOption
:=
TJSObject
.
new
;
taskOption
[
'taskId'
]
:=
ATaskId
;
taskOption
[
'taskDisplay'
]
:=
ATaskDisplay
;
FTaskOptions
.
push
(
taskOption
);
end
else
begin
if
taskIndex
<
0
then
Exit
;
// Rows already using this task keep it; it is only removed from the dropdown options
FTaskOptions
.
splice
(
taskIndex
,
1
);
end
;
if
xdwdsTimeEntries
.
Active
then
begin
CaptureTableScroll
;
RenderTable
;
end
;
end
;
...
...
@@ -1773,17 +1999,27 @@ begin
Exit
;
end
;
if
SameText
(
fieldName
,
'taskDate'
)
and
(
FEditingEntryId
>
0
)
then
begin
GotoRowIndex
(
idx
);
if
not
xdwdsTimeEntries
.
Eof
then
if
xdwdsTimeEntriesentryId
.
AsInteger
=
FEditingEntryId
then
Exit
;
end
;
console
.
log
(
'EditorBlur: SAVE idx='
+
IntToStr
(
idx
)
+
' field='
+
fieldName
);
SaveField
(
idx
,
fieldName
);
end
;
[
async
]
procedure
TFTimeEntries
.
SaveField
(
AIndex
:
Integer
;
const
AFieldName
:
string
)
;
[
async
]
function
TFTimeEntries
.
SaveField
(
AIndex
:
Integer
;
const
AFieldName
:
string
):
Boolean
;
var
response
:
TXDataClientResponse
;
payload
:
TJSObject
;
fieldValue
:
string
;
begin
Result
:=
False
;
if
not
xdwdsTimeEntries
.
Active
then
Exit
;
...
...
@@ -1831,8 +2067,11 @@ begin
'ITimeEntryService.SaveTimeEntryField'
,
[
payload
]
));
Result
:=
True
;
console
.
log
(
'SaveField: response='
+
string
(
TJSJSON
.
stringify
(
response
.
Result
)));
if
SameText
(
AFieldName
,
'hours'
)
or
SameText
(
AFieldName
,
'taskDate'
)
then
UpdateTotalHours
;
except
on
E
:
EXDataClientRequestException
do
begin
...
...
emT3Web/css/time-entries.css
View file @
e56dc151
...
...
@@ -49,4 +49,18 @@
.time-dd-toggle.is-invalid
{
border-color
:
var
(
--bs-danger
)
!important
;
padding-right
:
calc
(
1.5em
+
.75rem
);
}
.time-row-saved
>
td
{
animation
:
time-row-saved-flash
900ms
ease-out
;
}
@keyframes
time-row-saved-flash
{
from
{
box-shadow
:
inset
0
0
0
999px
rgba
(
25
,
135
,
84
,
0.25
);
}
to
{
box-shadow
:
inset
0
0
0
999px
rgba
(
25
,
135
,
84
,
0
);
}
}
\ No newline at end of file
emT3Web/emT3web.dproj
View file @
e56dc151
...
...
@@ -94,15 +94,15 @@
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.
3
.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.
4
.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>9</VerInfo_MinorVer>
<VerInfo_Release>4</VerInfo_Release>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<VerInfo_Release>3</VerInfo_Release>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSWebOutputPath>..\emT3XDataServer\bin\static</TMSWebOutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
...
...
emT3Web/uTaskPickerOffCanvas.pas
View file @
e56dc151
...
...
@@ -6,7 +6,15 @@ uses
System
.
SysUtils
,
JS
,
Web
,
XData
.
Web
.
Client
;
type
TTaskPickedProc
=
reference
to
procedure
(
ARowIndex
:
Integer
;
const
ATaskId
,
ATaskDisplay
:
string
);
TTaskPickedProc
=
reference
to
procedure
(
ARowIndex
:
Integer
;
const
ATaskId
,
ATaskDisplay
:
string
);
TTaskFocusChangedProc
=
reference
to
procedure
(
const
ATaskId
,
ATaskDisplay
:
string
;
AFocused
:
Boolean
);
TTaskPickerOffCanvas
=
class
private
...
...
@@ -14,45 +22,68 @@ type
FUserId
:
string
;
FRowIndex
:
Integer
;
FOnTaskPicked
:
TTaskPickedProc
;
FOnFocusChanged
:
TTaskFocusChangedProc
;
FCustomers
:
TJSArray
;
FProjects
:
TJSArray
;
FTasks
:
TJSArray
;
FFocusedTasks
:
TJSArray
;
FSavedFocus
:
Boolean
;
FSaving
:
Boolean
;
FPrefillToken
:
Integer
;
procedure
BindEvents
;
procedure
ClearSelect
(
const
AElementId
:
string
;
ADisabled
:
Boolean
);
procedure
FillSelect
(
const
AElementId
:
string
;
AItems
:
TJSArray
);
procedure
FilterProjects
(
const
ACustomerId
:
string
);
function
SelectedValue
(
const
AElementId
:
string
):
string
;
function
FindDisplay
(
AItems
:
TJSArray
;
const
AValue
:
string
):
string
;
function
FocusCheckbox
:
TJSHTMLInputElement
;
function
IsTaskFocused
(
const
ATaskId
:
string
):
Boolean
;
procedure
SetTaskFocused
(
const
ATaskId
,
ATaskDisplay
:
string
;
AFocused
:
Boolean
);
procedure
SetStatus
(
const
AMessage
:
string
);
procedure
ClearStatus
;
procedure
SetSaveEnabled
(
AEnabled
:
Boolean
);
procedure
SetButtonEnabled
(
const
AElementId
:
string
;
AEnabled
:
Boolean
);
procedure
UpdateButtons
;
procedure
Show
;
procedure
Hide
;
[
async
]
procedure
LoadCustomers
;
[
async
]
procedure
LoadProjects
(
const
ACustomerId
:
string
);
[
async
]
procedure
LoadTasks
(
const
AProjectId
:
string
);
[
async
]
procedure
PreselectTask
(
const
ATaskId
:
string
;
AToken
:
Integer
);
procedure
SetSelectValue
(
const
AElementId
,
AValue
:
string
);
[
async
]
procedure
CustomerChanged
(
Event
:
TJSEvent
);
procedure
CustomerChanged
(
Event
:
TJSEvent
);
[
async
]
procedure
ProjectChanged
(
Event
:
TJSEvent
);
procedure
TaskChanged
(
Event
:
TJSEvent
);
procedure
SaveClicked
(
Event
:
TJSEvent
);
procedure
FocusCheckboxChanged
(
Event
:
TJSEvent
);
procedure
UpdateFocusCheckbox
;
procedure
SelectClicked
(
Event
:
TJSEvent
);
[
async
]
procedure
SaveClicked
(
Event
:
TJSEvent
);
public
constructor
Create
(
AClient
:
TXDataWebClient
;
AOnTaskPicked
:
TTaskPickedProc
);
[
async
]
procedure
Open
(
const
AUserId
:
string
;
ARowIndex
:
Integer
);
constructor
Create
(
AClient
:
TXDataWebClient
;
AOnTaskPicked
:
TTaskPickedProc
;
AOnFocusChanged
:
TTaskFocusChangedProc
);
[
async
]
procedure
Open
(
const
AUserId
:
string
;
ARowIndex
:
Integer
;
AFocusedTasks
:
TJSArray
;
const
ATaskId
:
string
);
end
;
implementation
constructor
TTaskPickerOffCanvas
.
Create
(
AClient
:
TXDataWebClient
;
AOnTaskPicked
:
TTaskPickedProc
);
constructor
TTaskPickerOffCanvas
.
Create
(
AClient
:
TXDataWebClient
;
AOnTaskPicked
:
TTaskPickedProc
;
AOnFocusChanged
:
TTaskFocusChangedProc
);
begin
inherited
Create
;
FClient
:=
AClient
;
FOnTaskPicked
:=
AOnTaskPicked
;
FOnFocusChanged
:=
AOnFocusChanged
;
FCustomers
:=
TJSArray
.
new
;
FProjects
:=
TJSArray
.
new
;
FTasks
:=
TJSArray
.
new
;
FFocusedTasks
:=
TJSArray
.
new
;
FRowIndex
:=
-
1
;
FSavedFocus
:=
False
;
FSaving
:=
False
;
FPrefillToken
:=
0
;
BindEvents
;
end
;
...
...
@@ -73,9 +104,17 @@ begin
if
Assigned
(
el
)
then
el
.
addEventListener
(
'change'
,
TJSEventHandler
(@
TaskChanged
));
el
:=
TJSHTMLElement
(
document
.
getElementById
(
'task_picker_add_focus'
));
if
Assigned
(
el
)
then
el
.
addEventListener
(
'change'
,
TJSEventHandler
(@
FocusCheckboxChanged
));
el
:=
TJSHTMLElement
(
document
.
getElementById
(
'btn_task_picker_save'
));
if
Assigned
(
el
)
then
el
.
addEventListener
(
'click'
,
TJSEventHandler
(@
SaveClicked
));
el
:=
TJSHTMLElement
(
document
.
getElementById
(
'btn_task_picker_select'
));
if
Assigned
(
el
)
then
el
.
addEventListener
(
'click'
,
TJSEventHandler
(@
SelectClicked
));
end
;
procedure
TTaskPickerOffCanvas
.
ClearSelect
(
const
AElementId
:
string
;
ADisabled
:
Boolean
);
...
...
@@ -115,6 +154,28 @@ begin
selectEl
.
disabled
:=
False
;
end
;
procedure
TTaskPickerOffCanvas
.
FilterProjects
(
const
ACustomerId
:
string
);
var
filteredProjects
:
TJSArray
;
project
:
TJSObject
;
i
:
Integer
;
begin
//create an empty filtered project list
filteredProjects
:=
TJSArray
.
new
;
//loop through the full project list
for
i
:=
0
to
FProjects
.
length
-
1
do
begin
project
:=
TJSObject
(
FProjects
[
i
]);
if
(
ACustomerId
=
''
)
or
(
string
(
project
[
'customerId'
])
=
ACustomerId
)
then
begin
filteredProjects
.
push
(
project
);
end
;
end
;
FillSelect
(
'task_picker_project'
,
filteredProjects
)
end
;
function
TTaskPickerOffCanvas
.
SelectedValue
(
const
AElementId
:
string
):
string
;
var
selectEl
:
TJSHTMLSelectElement
;
...
...
@@ -141,6 +202,58 @@ begin
end
;
end
;
function
TTaskPickerOffCanvas
.
FocusCheckbox
:
TJSHTMLInputElement
;
begin
Result
:=
TJSHTMLInputElement
(
document
.
getElementById
(
'task_picker_add_focus'
));
end
;
function
TTaskPickerOffCanvas
.
IsTaskFocused
(
const
ATaskId
:
string
):
Boolean
;
var
taskOption
:
TJSObject
;
i
:
Integer
;
begin
Result
:=
False
;
if
ATaskId
=
''
then
Exit
;
for
i
:=
0
to
FFocusedTasks
.
length
-
1
do
begin
taskOption
:=
TJSObject
(
FFocusedTasks
[
i
]);
if
string
(
taskOption
[
'taskId'
])
=
ATaskId
then
Exit
(
True
);
end
;
end
;
procedure
TTaskPickerOffCanvas
.
SetTaskFocused
(
const
ATaskId
,
ATaskDisplay
:
string
;
AFocused
:
Boolean
);
var
taskOption
:
TJSObject
;
i
:
Integer
;
begin
// Keep the picker's own copy of the focused list in sync with the server
for
i
:=
FFocusedTasks
.
length
-
1
downto
0
do
begin
taskOption
:=
TJSObject
(
FFocusedTasks
[
i
]);
if
string
(
taskOption
[
'taskId'
])
=
ATaskId
then
begin
if
AFocused
then
Exit
;
FFocusedTasks
.
splice
(
i
,
1
);
end
;
end
;
if
AFocused
then
begin
taskOption
:=
TJSObject
.
new
;
taskOption
[
'taskId'
]
:=
ATaskId
;
taskOption
[
'taskDisplay'
]
:=
ATaskDisplay
;
FFocusedTasks
.
push
(
taskOption
);
end
;
end
;
procedure
TTaskPickerOffCanvas
.
SetStatus
(
const
AMessage
:
string
);
var
el
:
TJSHTMLElement
;
...
...
@@ -165,15 +278,38 @@ begin
el
.
classList
.
add
(
'd-none'
);
end
;
procedure
TTaskPickerOffCanvas
.
Set
SaveEnabled
(
AEnabled
:
Boolean
);
procedure
TTaskPickerOffCanvas
.
Set
ButtonEnabled
(
const
AElementId
:
string
;
AEnabled
:
Boolean
);
var
btn
:
TJSHTMLButtonElement
;
begin
btn
:=
TJSHTMLButtonElement
(
document
.
getElementById
(
'btn_task_picker_save'
));
btn
:=
TJSHTMLButtonElement
(
document
.
getElementById
(
AElementId
));
if
Assigned
(
btn
)
then
btn
.
disabled
:=
not
AEnabled
;
end
;
procedure
TTaskPickerOffCanvas
.
UpdateButtons
;
var
hasTask
:
Boolean
;
focusChanged
:
Boolean
;
addFocus
:
TJSHTMLInputElement
;
begin
// Select: any picked task can be sent to the grid.
// Save: only when the Focused Task checkbox differs from what is saved on the server.
hasTask
:=
SelectedValue
(
'task_picker_task'
)
<>
''
;
addFocus
:=
FocusCheckbox
;
focusChanged
:=
False
;
if
Assigned
(
addFocus
)
then
begin
addFocus
.
disabled
:=
(
not
hasTask
)
or
FSaving
;
focusChanged
:=
addFocus
.
checked
<>
FSavedFocus
;
end
;
SetButtonEnabled
(
'btn_task_picker_select'
,
hasTask
and
not
FSaving
);
SetButtonEnabled
(
'btn_task_picker_save'
,
hasTask
and
focusChanged
and
not
FSaving
);
end
;
procedure
TTaskPickerOffCanvas
.
Show
;
begin
asm
...
...
@@ -202,8 +338,7 @@ var
resultObj
:
TJSObject
;
begin
response
:=
await
(
FClient
.
RawInvokeAsync
(
'ITimeEntryService.GetTaskPickerCustomers'
,
[
FUserId
]
'ITimeEntryService.GetTaskPickerCustomers'
,
[]
));
resultObj
:=
TJSObject
(
response
.
Result
);
...
...
@@ -218,12 +353,11 @@ var
begin
response
:=
await
(
FClient
.
RawInvokeAsync
(
'ITimeEntryService.GetTaskPickerProjects'
,
[
FUserId
,
ACustomerId
]
[
ACustomerId
]
));
resultObj
:=
TJSObject
(
response
.
Result
);
FProjects
:=
TJSArray
(
resultObj
[
'options'
]);
FillSelect
(
'task_picker_project'
,
FProjects
);
end
;
[
async
]
procedure
TTaskPickerOffCanvas
.
LoadTasks
(
const
AProjectId
:
string
);
...
...
@@ -233,7 +367,7 @@ var
begin
response
:=
await
(
FClient
.
RawInvokeAsync
(
'ITimeEntryService.GetTaskPickerTasks'
,
[
FUserId
,
AProjectId
]
[
AProjectId
]
));
resultObj
:=
TJSObject
(
response
.
Result
);
...
...
@@ -241,39 +375,121 @@ begin
FillSelect
(
'task_picker_task'
,
FTasks
);
end
;
[
async
]
procedure
TTaskPickerOffCanvas
.
CustomerChanged
(
Event
:
TJSEvent
);
procedure
TTaskPickerOffCanvas
.
SetSelectValue
(
const
AElementId
,
AValue
:
string
);
var
selectEl
:
TJSHTMLSelectElement
;
begin
// Setting .value in code does not fire 'change', so the cascade handlers do not run
selectEl
:=
TJSHTMLSelectElement
(
document
.
getElementById
(
AElementId
));
if
Assigned
(
selectEl
)
then
selectEl
.
value
:=
AValue
;
end
;
[
async
]
procedure
TTaskPickerOffCanvas
.
PreselectTask
(
const
ATaskId
:
string
;
AToken
:
Integer
);
var
response
:
TXDataClientResponse
;
context
:
TJSObject
;
project
:
TJSObject
;
projectId
:
string
;
customerId
:
string
;
projectFound
:
Boolean
;
i
:
Integer
;
begin
response
:=
await
(
FClient
.
RawInvokeAsync
(
'ITimeEntryService.GetTaskPickerTaskContext'
,
[
ATaskId
]
));
// Picker was reopened or the user changed a dropdown while this was loading
if
AToken
<>
FPrefillToken
then
Exit
;
context
:=
TJSObject
(
response
.
Result
);
projectId
:=
string
(
context
[
'projectId'
]);
if
projectId
=
''
then
Exit
;
// FProjects only holds ACTIVE projects; a task under any other project opens blank
projectFound
:=
False
;
customerId
:=
''
;
for
i
:=
0
to
FProjects
.
length
-
1
do
begin
project
:=
TJSObject
(
FProjects
[
i
]);
if
string
(
project
[
'value'
])
=
projectId
then
begin
customerId
:=
string
(
project
[
'customerId'
]);
projectFound
:=
True
;
Break
;
end
;
end
;
if
not
projectFound
then
Exit
;
SetSelectValue
(
'task_picker_customer'
,
customerId
);
FilterProjects
(
customerId
);
SetSelectValue
(
'task_picker_project'
,
projectId
);
await
(
LoadTasks
(
projectId
));
if
AToken
<>
FPrefillToken
then
Exit
;
// If the task is not in the list (e.g. moved project) the task select just stays blank
SetSelectValue
(
'task_picker_task'
,
ATaskId
);
UpdateFocusCheckbox
;
end
;
procedure
TTaskPickerOffCanvas
.
CustomerChanged
(
Event
:
TJSEvent
);
var
customerId
:
string
;
begin
Inc
(
FPrefillToken
);
// user took over; abandon any prefill still loading
ClearStatus
;
SetSaveEnabled
(
False
);
ClearSelect
(
'task_picker_project'
,
True
);
ClearSelect
(
'task_picker_task'
,
True
);
UpdateFocusCheckbox
;
customerId
:=
SelectedValue
(
'task_picker_customer'
);
if
customerId
=
''
then
Exit
;
try
await
(
LoadProjects
(
customerId
));
except
on
E
:
Exception
do
SetStatus
(
'Unable to load projects.'
);
end
;
FilterProjects
(
customerId
);
end
;
[
async
]
procedure
TTaskPickerOffCanvas
.
ProjectChanged
(
Event
:
TJSEvent
);
var
projectId
:
string
;
customerId
:
string
;
project
:
TJSObject
;
i
:
Integer
;
begin
Inc
(
FPrefillToken
);
// user took over; abandon any prefill still loading
ClearStatus
;
SetSaveEnabled
(
False
);
ClearSelect
(
'task_picker_task'
,
True
);
UpdateFocusCheckbox
;
projectId
:=
SelectedValue
(
'task_picker_project'
);
if
projectId
=
''
then
Exit
;
customerId
:=
SelectedValue
(
'task_picker_customer'
);
if
customerId
=
''
then
begin
for
i
:=
0
to
FProjects
.
length
-
1
do
begin
project
:=
TJSObject
(
FProjects
[
i
]);
if
string
(
project
[
'value'
])
=
projectId
then
begin
customerId
:=
string
(
project
[
'customerId'
]);
Break
;
end
;
end
;
TJSHTMLSelectElement
(
document
.
getElementById
(
'task_picker_customer'
)).
value
:=
customerId
;
FilterProjects
(
customerId
);
TJSHTMLSelectElement
(
document
.
getElementById
(
'task_picker_project'
)).
value
:=
projectId
;
end
;
try
await
(
LoadTasks
(
projectId
));
except
...
...
@@ -282,16 +498,36 @@ begin
end
;
end
;
procedure
TTaskPickerOffCanvas
.
UpdateFocusCheckbox
;
var
addFocus
:
TJSHTMLInputElement
;
begin
// Reset the checkbox to the task's saved focus state (discards any unsaved toggle)
FSavedFocus
:=
IsTaskFocused
(
SelectedValue
(
'task_picker_task'
));
addFocus
:=
FocusCheckbox
;
if
Assigned
(
addFocus
)
then
addFocus
.
checked
:=
FSavedFocus
;
UpdateButtons
;
end
;
procedure
TTaskPickerOffCanvas
.
TaskChanged
(
Event
:
TJSEvent
);
begin
Inc
(
FPrefillToken
);
// user took over; abandon any prefill still loading
ClearStatus
;
UpdateFocusCheckbox
;
end
;
procedure
TTaskPickerOffCanvas
.
FocusCheckboxChanged
(
Event
:
TJSEvent
);
begin
ClearStatus
;
SetSaveEnabled
(
SelectedValue
(
'task_picker_task'
)
<>
''
)
;
UpdateButtons
;
end
;
procedure
TTaskPickerOffCanvas
.
S
ave
Clicked
(
Event
:
TJSEvent
);
procedure
TTaskPickerOffCanvas
.
S
elect
Clicked
(
Event
:
TJSEvent
);
var
taskId
:
string
;
taskDisplay
:
string
;
begin
Event
.
preventDefault
;
...
...
@@ -302,32 +538,136 @@ begin
Exit
;
end
;
taskDisplay
:=
FindDisplay
(
FTasks
,
taskId
)
;
ClearStatus
;
// Sends the task to the grid only; focus changes happen through Save
if
Assigned
(
FOnTaskPicked
)
then
FOnTaskPicked
(
FRowIndex
,
taskId
,
taskDisplay
);
FOnTaskPicked
(
FRowIndex
,
taskId
,
FindDisplay
(
FTasks
,
taskId
)
);
Hide
;
end
;
[
async
]
procedure
TTaskPickerOffCanvas
.
Open
(
const
AUserId
:
string
;
ARowIndex
:
Integer
);
[
async
]
procedure
TTaskPickerOffCanvas
.
SaveClicked
(
Event
:
TJSEvent
);
var
taskId
:
string
;
taskDisplay
:
string
;
methodName
:
string
;
addFocus
:
TJSHTMLInputElement
;
focused
:
Boolean
;
begin
Event
.
preventDefault
;
if
FSaving
then
Exit
;
taskId
:=
SelectedValue
(
'task_picker_task'
);
if
taskId
=
''
then
begin
SetStatus
(
'Select a task.'
);
Exit
;
end
;
addFocus
:=
FocusCheckbox
;
if
not
Assigned
(
addFocus
)
then
Exit
;
focused
:=
addFocus
.
checked
;
if
focused
=
FSavedFocus
then
begin
UpdateButtons
;
Exit
;
end
;
taskDisplay
:=
FindDisplay
(
FTasks
,
taskId
);
if
focused
then
methodName
:=
'ITimeEntryService.AddTaskToFocus'
else
methodName
:=
'ITimeEntryService.RemoveTaskFromFocus'
;
ClearStatus
;
FSaving
:=
True
;
UpdateButtons
;
try
try
await
(
FClient
.
RawInvokeAsync
(
methodName
,
[
FUserId
,
taskId
]));
except
on
E
:
EXDataClientRequestException
do
begin
SetStatus
(
E
.
ErrorResult
.
ErrorMessage
);
Exit
;
end
;
on
E
:
Exception
do
begin
SetStatus
(
'Unable to save focused task.'
);
Exit
;
end
;
end
;
SetTaskFocused
(
taskId
,
taskDisplay
,
focused
);
// The user may have picked a different task while the request was running
if
SelectedValue
(
'task_picker_task'
)
=
taskId
then
FSavedFocus
:=
focused
;
if
Assigned
(
FOnFocusChanged
)
then
FOnFocusChanged
(
taskId
,
taskDisplay
,
focused
);
finally
FSaving
:=
False
;
UpdateButtons
;
end
;
end
;
[
async
]
procedure
TTaskPickerOffCanvas
.
Open
(
const
AUserId
:
string
;
ARowIndex
:
Integer
;
AFocusedTasks
:
TJSArray
;
const
ATaskId
:
string
);
var
token
:
Integer
;
begin
FUserId
:=
AUserId
;
FRowIndex
:=
ARowIndex
;
Inc
(
FPrefillToken
);
token
:=
FPrefillToken
;
// Work on a copy so the grid's task options are only changed through FOnFocusChanged
if
Assigned
(
AFocusedTasks
)
then
FFocusedTasks
:=
AFocusedTasks
.
slice
(
0
)
else
FFocusedTasks
:=
TJSArray
.
new
;
ClearStatus
;
SetSaveEnabled
(
False
);
ClearSelect
(
'task_picker_customer'
,
True
);
ClearSelect
(
'task_picker_project'
,
True
);
ClearSelect
(
'task_picker_task'
,
True
);
UpdateFocusCheckbox
;
Show
;
try
await
(
LoadCustomers
);
await
(
LoadProjects
(
''
));
FilterProjects
(
''
);
except
on
E
:
Exception
do
begin
SetStatus
(
'Unable to load customers or projects.'
);
Exit
;
end
;
end
;
// Row already has a task: open on its customer, project and task
if
(
ATaskId
=
''
)
or
(
token
<>
FPrefillToken
)
then
Exit
;
try
await
(
PreselectTask
(
ATaskId
,
token
));
except
on
E
:
Exception
do
SetStatus
(
'Unable to load
customers
.'
);
SetStatus
(
'Unable to load
this row''s task
.'
);
end
;
end
;
...
...
emT3XDataServer/Source/Api.Database.dfm
View file @
e56dc151
...
...
@@ -7,10 +7,10 @@ object ApiDatabase: TApiDatabase
ProviderName = 'MySQL'
Database = 'eTask'
Username = 'root'
Server = '192.168.
102.133
'
Server = '192.168.
90.131
'
LoginPrompt = False
Left = 43
5
Top = 3
59
Left = 43
9
Top = 3
63
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end
object MySQLUniProvider1: TMySQLUniProvider
...
...
@@ -265,6 +265,60 @@ object ApiDatabase: TApiDatabase
Value = nil
end>
end
object uqCopyTaskItem: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'insert into task_items ('
' TASK_ID,'
' ITEM_NUM,'
' APPLICATION,'
' APP_VERSION,'
' TASK_DATE,'
' STATUS_DATE,'
' REPORTED_BY,'
' ASSIGNED_TO,'
' STATUS,'
' FIXED_VERSION,'
' FORM_SECTION,'
' ISSUE,'
' NOTES'
')'
'select'
' TASK_ID,'
' :ITEM_NUM,'
' APPLICATION,'
' APP_VERSION,'
' TASK_DATE,'
' STATUS_DATE,'
' REPORTED_BY,'
' ASSIGNED_TO,'
' STATUS,'
' FIXED_VERSION,'
' FORM_SECTION,'
' ISSUE,'
' NOTES'
'from task_items'
'where TASK_ID = :TASK_ID'
' and TASK_ITEM_ID = :TASK_ITEM_ID')
Left = 708
Top = 302
ParamData = <
item
DataType = ftInteger
Name = 'ITEM_NUM'
Value = nil
end
item
DataType = ftString
Name = 'TASK_ID'
Value = nil
end
item
DataType = ftInteger
Name = 'TASK_ITEM_ID'
Value = nil
end>
end
object uqTaskHeader: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
...
...
@@ -565,7 +619,8 @@ object ApiDatabase: TApiDatabase
'update task_items'
'set ITEM_NUM = ITEM_NUM + 1'
'where TASK_ID = :TASK_ID'
' and ITEM_NUM > :INSERT_AFTER_ITEM_NUM')
' and ITEM_NUM > :INSERT_AFTER_ITEM_NUM'
'order by ITEM_NUM desc')
Left = 538
Top = 76
ParamData = <
...
...
@@ -798,7 +853,7 @@ object ApiDatabase: TApiDatabase
'where target_task.TASK_ID = :TASK_ID'
'order by tia.NAME')
Left = 58
Top = 31
4
Top = 31
6
ParamData = <
item
DataType = ftUnknown
...
...
@@ -1224,50 +1279,47 @@ object ApiDatabase: TApiDatabase
SQL.Strings = (
'select distinct'
' c.CUSTOMER_ID,'
' c.
SHORT_NAME as CUSTOMER_SHORT
_NAME'
'from
user_project up
'
'join project p on p.
PROJECT_ID = up.PROJECT
_ID'
'
join customers c on c.CUSTOMER_ID = p.CUSTOMER_ID'
'
where up.USER_ID = :USER_ID
'
'
order by c.SHORT_NAME
')
Left = 40
2
' c.
NAME as CUSTOMER
_NAME'
'from
customers c
'
'join project p on p.
CUSTOMER_ID = c.CUSTOMER
_ID'
'
where p.STATUS = '#39'ACTIVE'#39
'
order by c.NAME
'
'')
Left = 40
4
Top = 494
ParamData = <
item
DataType = ftUnknown
Name = 'USER_ID'
Value = nil
end>
object uqTaskPickerCustomersCUSTOMER_ID: TStringField
FieldName = 'CUSTOMER_ID'
Size = 7
end
object uqTaskPickerCustomersCUSTOMER_
SHORT_
NAME: TStringField
FieldName = 'CUSTOMER_
SHORT_
NAME'
Size =
1
0
object uqTaskPickerCustomersCUSTOMER_NAME: TStringField
FieldName = 'CUSTOMER_NAME'
Size =
3
0
end
end
object uqTaskPickerProjects: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select
distinct
'
'select'
' p.PROJECT_ID,'
' p.NAME as PROJECT_NAME'
'from user_project up'
'join project p on p.PROJECT_ID = up.PROJECT_ID'
'where up.USER_ID = :USER_ID'
' and p.CUSTOMER_ID = :CUSTOMER_ID'
'order by p.NAME')
' p.NAME as PROJECT_NAME,'
' p.CUSTOMER_ID'
'from project p '
'join customers c on c.CUSTOMER_ID = p.CUSTOMER_ID'
'where p.STATUS = '#39'ACTIVE'#39
'and (:CUSTOMER_ID = '#39#39' or p.CUSTOMER_ID = :CUSTOMER_ID)'
'order by p.NAME'
''
''
''
''
''
''
'')
Left = 544
Top = 494
ParamData = <
item
DataType = ftUnknown
Name = 'USER_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'CUSTOMER_ID'
Value = nil
end>
...
...
@@ -1280,13 +1332,17 @@ object ApiDatabase: TApiDatabase
FieldName = 'PROJECT_NAME'
Size = 30
end
object uqTaskPickerProjectsCUSTOMER_ID: TStringField
FieldName = 'CUSTOMER_ID'
Size = 7
end
end
object uqTaskPickerTasks: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select distinct'
' t.TASK_ID,'
' c.SHORT_NAME as CUSTOMER_
SHORT_
NAME,'
' c.SHORT_NAME as CUSTOMER_NAME,'
' p.NAME as PROJECT_NAME,'
' t.TASK_NUM_1,'
' t.TASK_NUM_2,'
...
...
@@ -1298,9 +1354,8 @@ object ApiDatabase: TApiDatabase
'from tasks t'
'join project p on p.PROJECT_ID = t.PROJECT_ID'
'join customers c on c.CUSTOMER_ID = p.CUSTOMER_ID'
'join user_project up on up.PROJECT_ID = p.PROJECT_ID'
'where up.USER_ID = :USER_ID'
' and t.PROJECT_ID = :PROJECT_ID'
' where t.PROJECT_ID = :PROJECT_ID'
' and p.STATUS = '#39'ACTIVE'#39
'order by'
' t.TASK_NUM_1,'
' t.TASK_NUM_2,'
...
...
@@ -1308,17 +1363,13 @@ object ApiDatabase: TApiDatabase
' t.TASK_NUM_4,'
' t.TASK_NUM_5,'
' t.TASK_NUM_6,'
' t.SUBJECT')
' t.SUBJECT'
'')
Left = 676
Top = 502
ParamData = <
item
DataType = ftUnknown
Name = 'USER_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'PROJECT_ID'
Value = nil
end>
...
...
@@ -1327,10 +1378,10 @@ object ApiDatabase: TApiDatabase
Required = True
Size = 7
end
object uqTaskPickerTasksCUSTOMER_
SHORT_
NAME: TStringField
FieldName = 'CUSTOMER_
SHORT_
NAME'
object uqTaskPickerTasksCUSTOMER_NAME: TStringField
FieldName = 'CUSTOMER_NAME'
ReadOnly = True
Size =
1
0
Size =
3
0
end
object uqTaskPickerTasksPROJECT_NAME: TStringField
FieldName = 'PROJECT_NAME'
...
...
emT3XDataServer/Source/Api.Database.pas
View file @
e56dc151
...
...
@@ -19,6 +19,7 @@ type
uqSaveTaskRow
:
TUniQuery
;
uqTaskItems
:
TUniQuery
;
uqAddTaskRow
:
TUniQuery
;
uqCopyTaskItem
:
TUniQuery
;
uqTaskItemsTASK_ID
:
TStringField
;
uqTaskItemsITEM_NUM
:
TShortintField
;
uqTaskItemsAPPLICATION
:
TStringField
;
...
...
@@ -93,12 +94,11 @@ type
uqTaskPickerCustomers
:
TUniQuery
;
uqTaskPickerProjects
:
TUniQuery
;
uqTaskPickerTasks
:
TUniQuery
;
uqTaskPickerCustomersCUSTOMER_ID
:
TStringField
;
uqTaskPickerCustomersCUSTOMER_SHORT_NAME
:
TStringField
;
uqTaskPickerProjectsPROJECT_ID
:
TStringField
;
uqTaskPickerProjectsPROJECT_NAME
:
TStringField
;
uqTaskPickerProjectsCUSTOMER_ID
:
TStringField
;
uqTaskPickerTasksTASK_ID
:
TStringField
;
uqTaskPickerTasksCUSTOMER_
SHORT_
NAME
:
TStringField
;
uqTaskPickerTasksCUSTOMER_NAME
:
TStringField
;
uqTaskPickerTasksPROJECT_NAME
:
TStringField
;
uqTaskPickerTasksTASK_NUM_1
:
TIntegerField
;
uqTaskPickerTasksTASK_NUM_2
:
TIntegerField
;
...
...
@@ -107,6 +107,8 @@ type
uqTaskPickerTasksTASK_NUM_5
:
TIntegerField
;
uqTaskPickerTasksTASK_NUM_6
:
TIntegerField
;
uqTaskPickerTasksTASK_SUBJECT
:
TStringField
;
uqTaskPickerCustomersCUSTOMER_ID
:
TStringField
;
uqTaskPickerCustomersCUSTOMER_NAME
:
TStringField
;
procedure
DataModuleCreate
(
Sender
:
TObject
);
procedure
uqUsersCalcFields
(
DataSet
:
TDataSet
);
private
...
...
emT3XDataServer/Source/TaskItem.Service.pas
View file @
e56dc151
...
...
@@ -120,6 +120,7 @@ type
[
HttpGet
]
function
GetTaskItems
(
taskId
:
string
):
TTaskItemsResponse
;
[
HttpPost
]
function
AddTaskRow
(
taskId
:
string
;
insertAfterItemNum
:
Integer
):
Boolean
;
[
HttpPost
]
function
CopyTaskRow
(
taskId
:
string
;
sourceTaskItemId
:
Integer
):
Integer
;
[
HttpPost
]
function
AddAssignedName
(
taskId
:
string
;
name
:
string
):
TTaskUserOptionsResponse
;
[
HttpPost
]
function
RenameAssignedName
(
taskId
:
string
;
oldName
:
string
;
newName
:
string
):
TTaskUserOptionsResponse
;
...
...
emT3XDataServer/Source/TaskItem.ServiceImpl.pas
View file @
e56dc151
...
...
@@ -19,6 +19,7 @@ type
function
FindAssignedOptionId
(
const
taskId
,
name
:
string
):
string
;
function
FindAssignedOptionName
(
const
taskId
,
name
:
string
):
string
;
function
AddTaskRow
(
taskId
:
string
;
insertAfterItemNum
:
Integer
):
Boolean
;
function
CopyTaskRow
(
taskId
:
string
;
sourceTaskItemId
:
Integer
):
Integer
;
function
BuildAssignedOptionsResponse
(
const
taskId
:
string
):
TTaskUserOptionsResponse
;
function
AddAssignedName
(
taskId
:
string
;
name
:
string
):
TTaskUserOptionsResponse
;
function
RenameAssignedName
(
taskId
:
string
;
oldName
:
string
;
newName
:
string
):
TTaskUserOptionsResponse
;
...
...
@@ -50,6 +51,7 @@ implementation
procedure
TTaskItemService
.
AfterConstruction
;
begin
inherited
;
Logger
.
Log
(
4
,
'TaskItemService.AfterConstruction->TApiDatabase.Create(nil)'
);
apiDB
:=
TApiDatabase
.
Create
(
nil
);
Logger
.
Log
(
4
,
'TaskItemService.AfterConstruction - apiDB created'
);
end
;
...
...
@@ -272,6 +274,66 @@ begin
end
;
function
TTaskItemService
.
CopyTaskRow
(
taskId
:
string
;
sourceTaskItemId
:
Integer
):
Integer
;
var
q
:
TUniQuery
;
sourceItemNum
:
Integer
;
begin
Logger
.
Log
(
4
,
Format
(
'TaskItemService.CopyTaskRow - TASK_ID="%s" SOURCE_TASK_ITEM_ID="%d"'
,
[
taskId
,
sourceTaskItemId
]));
apiDB
.
uqGetTaskRowPosition
.
Close
;
apiDB
.
uqGetTaskRowPosition
.
ParamByName
(
'TASK_ID'
).
AsString
:=
taskId
;
apiDB
.
uqGetTaskRowPosition
.
ParamByName
(
'TASK_ITEM_ID'
).
AsInteger
:=
sourceTaskItemId
;
apiDB
.
uqGetTaskRowPosition
.
Open
;
if
apiDB
.
uqGetTaskRowPosition
.
IsEmpty
then
begin
Logger
.
Log
(
2
,
Format
(
'TaskItemService.CopyTaskRow - row not found TASK_ID="%s" TASK_ITEM_ID="%d"'
,
[
taskId
,
sourceTaskItemId
]));
raise
Exception
.
Create
(
'Task row not found.'
);
end
;
sourceItemNum
:=
apiDB
.
uqGetTaskRowPositionITEM_NUM
.
AsInteger
;
q
:=
TUniQuery
.
Create
(
nil
);
try
q
.
Connection
:=
apiDB
.
ucETaskApi
;
apiDB
.
ucETaskApi
.
StartTransaction
;
try
apiDB
.
uqShiftTaskRowsForInsert
.
Close
;
apiDB
.
uqShiftTaskRowsForInsert
.
ParamByName
(
'TASK_ID'
).
AsString
:=
taskId
;
apiDB
.
uqShiftTaskRowsForInsert
.
ParamByName
(
'INSERT_AFTER_ITEM_NUM'
).
AsInteger
:=
sourceItemNum
;
apiDB
.
uqShiftTaskRowsForInsert
.
ExecSQL
;
apiDB
.
uqCopyTaskItem
.
Close
;
apiDB
.
uqCopyTaskItem
.
ParamByName
(
'TASK_ID'
).
AsString
:=
taskId
;
apiDB
.
uqCopyTaskItem
.
ParamByName
(
'TASK_ITEM_ID'
).
AsInteger
:=
sourceTaskItemId
;
apiDB
.
uqCopyTaskItem
.
ParamByName
(
'ITEM_NUM'
).
AsInteger
:=
sourceItemNum
+
1
;
apiDB
.
uqCopyTaskItem
.
ExecSQL
;
if
apiDB
.
uqCopyTaskItem
.
RowsAffected
<>
1
then
raise
Exception
.
Create
(
'Unable to copy the selected task row.'
);
q
.
SQL
.
Text
:=
'select LAST_INSERT_ID() as TASK_ITEM_ID'
;
q
.
Open
;
Result
:=
q
.
FieldByName
(
'TASK_ITEM_ID'
).
AsInteger
;
apiDB
.
ucETaskApi
.
Commit
;
Logger
.
Log
(
4
,
Format
(
'TaskItemService.CopyTaskRow - OK TASK_ID="%s" SOURCE_TASK_ITEM_ID="%d" TASK_ITEM_ID="%d" ITEM_NUM="%d"'
,
[
taskId
,
sourceTaskItemId
,
Result
,
sourceItemNum
+
1
]));
except
on
E
:
Exception
do
begin
if
apiDB
.
ucETaskApi
.
InTransaction
then
apiDB
.
ucETaskApi
.
Rollback
;
Logger
.
Log
(
2
,
'TaskItemService.CopyTaskRow - ERROR: '
+
E
.
Message
);
raise
;
end
;
end
;
finally
q
.
Free
;
end
;
end
;
procedure
TTaskItemService
.
MoveTaskRow
(
const
taskId
:
Integer
;
const
taskItemId
:
Integer
;
const
newItemNum
:
Integer
);
var
oldItemNum
:
Integer
;
...
...
emT3XDataServer/Source/TimeEntry.Service.pas
View file @
e56dc151
...
...
@@ -73,11 +73,11 @@ type
value
:
string
;
end
;
type
TTaskPickerOption
=
class
public
value
:
string
;
display
:
string
;
customerId
:
string
;
end
;
TTaskPickerOptionsResponse
=
class
...
...
@@ -87,6 +87,13 @@ type
destructor
Destroy
;
override
;
end
;
TTaskPickerTaskContext
=
class
public
taskId
:
string
;
projectId
:
string
;
customerId
:
string
;
end
;
[
ServiceContract
,
Model
(
API_MODEL
)]
ITimeEntryService
=
interface
(
IInvokable
)
[
'{B18BCD1E-B19A-4D25-BBA9-50A24FC4C690}'
]
...
...
@@ -95,9 +102,12 @@ type
[
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
;
function
GetTaskPickerProjects
(
userId
,
customerId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerTasks
(
userId
,
projectId
:
string
):
TTaskPickerOptionsResponse
;
[
HttpPost
]
function
AddTaskToFocus
(
userId
,
taskId
:
string
):
Boolean
;
[
HttpPost
]
function
RemoveTaskFromFocus
(
userId
,
taskId
:
string
):
Boolean
;
function
GetTaskPickerCustomers
:
TTaskPickerOptionsResponse
;
function
GetTaskPickerProjects
(
customerId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerTasks
(
projectId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerTaskContext
(
taskId
:
string
):
TTaskPickerTaskContext
;
end
;
implementation
...
...
emT3XDataServer/Source/TimeEntry.ServiceImpl.pas
View file @
e56dc151
...
...
@@ -34,10 +34,13 @@ type
function
AddTimeEntry
(
userId
,
taskDate
:
string
):
string
;
function
SaveTimeEntry
(
Item
:
TTimeEntrySave
):
string
;
function
SaveTimeEntryField
(
Item
:
TTimeEntryFieldSave
):
Boolean
;
function
GetTaskPickerCustomers
(
userId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerProjects
(
userId
,
customerId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerTasks
(
userId
,
projectId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerCustomers
:
TTaskPickerOptionsResponse
;
function
GetTaskPickerProjects
(
customerId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerTasks
(
projectId
:
string
):
TTaskPickerOptionsResponse
;
function
GetTaskPickerTaskContext
(
taskId
:
string
):
TTaskPickerTaskContext
;
function
DeleteTimeEntry
(
userId
:
string
;
entryId
:
Integer
):
Boolean
;
function
AddTaskToFocus
(
userId
,
taskId
:
string
):
Boolean
;
function
RemoveTaskFromFocus
(
userId
,
taskId
:
string
):
Boolean
;
end
;
implementation
...
...
@@ -50,7 +53,9 @@ uses
procedure
TTimeEntryService
.
AfterConstruction
;
begin
inherited
;
Logger
.
Log
(
4
,
'TTimeEntryService.AfterConstruction->TApiDatabase.Create(nil)'
);
apiDB
:=
TApiDatabase
.
Create
(
nil
);
Logger
.
Log
(
4
,
'TTimeEntryService.AfterConstruction - apiDB created'
)
end
;
...
...
@@ -130,7 +135,7 @@ var
leftText
:
string
;
taskText
:
string
;
begin
customerText
:=
FieldText
(
AQuery
,
'CUSTOMER_
SHORT_
NAME'
);
customerText
:=
FieldText
(
AQuery
,
'CUSTOMER_NAME'
);
projectText
:=
FieldText
(
AQuery
,
'PROJECT_NAME'
);
taskNumberText
:=
BuildTaskNumber
(
AQuery
);
subjectText
:=
FieldText
(
AQuery
,
'TASK_SUBJECT'
);
...
...
@@ -210,7 +215,7 @@ begin
' ti.CATEGORY, '
+
' cat.CODE_DESC as CATEGORY_DESC, '
+
' ti.SUMMARY, '
+
' c.SHORT_NAME as CUSTOMER_
SHORT_
NAME, '
+
' c.SHORT_NAME as CUSTOMER_NAME, '
+
' p.NAME as PROJECT_NAME, '
+
' t.TASK_NUM_1, '
+
' t.TASK_NUM_2, '
+
...
...
@@ -290,7 +295,7 @@ begin
tasksQuery
.
SQL
.
Text
:=
'select distinct '
+
' t.TASK_ID, '
+
' c.SHORT_NAME as CUSTOMER_
SHORT_
NAME, '
+
' c.SHORT_NAME as CUSTOMER_NAME, '
+
' p.NAME as PROJECT_NAME, '
+
' t.TASK_NUM_1, '
+
' t.TASK_NUM_2, '
+
...
...
@@ -764,15 +769,14 @@ begin
end
;
function
TTimeEntryService
.
GetTaskPickerCustomers
(
userId
:
string
)
:
TTaskPickerOptionsResponse
;
function
TTimeEntryService
.
GetTaskPickerCustomers
:
TTaskPickerOptionsResponse
;
begin
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.GetTaskPickerCustomers - USER_ID="%s"'
,
[
userId
])
);
Logger
.
Log
(
4
,
'TimeEntryService.GetTaskPickerCustomers Start'
);
Result
:=
TTaskPickerOptionsResponse
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
apiDB
.
uqTaskPickerCustomers
.
Close
;
apiDB
.
uqTaskPickerCustomers
.
ParamByName
(
'USER_ID'
).
AsString
:=
userId
;
apiDB
.
uqTaskPickerCustomers
.
Open
;
try
while
not
apiDB
.
uqTaskPickerCustomers
.
Eof
do
...
...
@@ -780,7 +784,7 @@ begin
AddTaskPickerOption
(
Result
,
FieldText
(
apiDB
.
uqTaskPickerCustomers
,
'CUSTOMER_ID'
),
FieldText
(
apiDB
.
uqTaskPickerCustomers
,
'CUSTOMER_
SHORT_
NAME'
)
FieldText
(
apiDB
.
uqTaskPickerCustomers
,
'CUSTOMER_NAME'
)
);
apiDB
.
uqTaskPickerCustomers
.
Next
;
...
...
@@ -791,15 +795,14 @@ begin
end
;
function
TTimeEntryService
.
GetTaskPickerProjects
(
userId
,
customerId
:
string
):
TTaskPickerOptionsResponse
;
function
TTimeEntryService
.
GetTaskPickerProjects
(
customerId
:
string
):
TTaskPickerOptionsResponse
;
begin
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.GetTaskPickerProjects -
USER_ID="%s" CUSTOMER_ID="%s"'
,
[
userId
,
customerId
]));
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.GetTaskPickerProjects -
CUSTOMER_ID="%s"'
,
[
customerId
]));
Result
:=
TTaskPickerOptionsResponse
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
apiDB
.
uqTaskPickerProjects
.
Close
;
apiDB
.
uqTaskPickerProjects
.
ParamByName
(
'USER_ID'
).
AsString
:=
userId
;
apiDB
.
uqTaskPickerProjects
.
ParamByName
(
'CUSTOMER_ID'
).
AsString
:=
customerId
;
apiDB
.
uqTaskPickerProjects
.
Open
;
try
...
...
@@ -811,6 +814,9 @@ begin
FieldText
(
apiDB
.
uqTaskPickerProjects
,
'PROJECT_NAME'
)
);
Result
.
options
[
Result
.
options
.
Count
-
1
].
customerId
:=
apiDB
.
uqTaskPickerProjectsCUSTOMER_ID
.
AsString
;
apiDB
.
uqTaskPickerProjects
.
Next
;
end
;
finally
...
...
@@ -819,15 +825,14 @@ begin
end
;
function
TTimeEntryService
.
GetTaskPickerTasks
(
userId
,
projectId
:
string
):
TTaskPickerOptionsResponse
;
function
TTimeEntryService
.
GetTaskPickerTasks
(
projectId
:
string
):
TTaskPickerOptionsResponse
;
begin
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.GetTaskPickerTasks -
USER_ID="%s" PROJECT_ID="%s"'
,
[
userId
,
projectId
]));
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.GetTaskPickerTasks -
PROJECT_ID="%s"'
,
[
projectId
]));
Result
:=
TTaskPickerOptionsResponse
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
apiDB
.
uqTaskPickerTasks
.
Close
;
apiDB
.
uqTaskPickerTasks
.
ParamByName
(
'USER_ID'
).
AsString
:=
userId
;
apiDB
.
uqTaskPickerTasks
.
ParamByName
(
'PROJECT_ID'
).
AsString
:=
projectId
;
apiDB
.
uqTaskPickerTasks
.
Open
;
try
...
...
@@ -847,6 +852,39 @@ begin
end
;
function
TTimeEntryService
.
GetTaskPickerTaskContext
(
taskId
:
string
):
TTaskPickerTaskContext
;
var
q
:
TUniQuery
;
begin
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.GetTaskPickerTaskContext - TASK_ID="%s"'
,
[
taskId
]));
Result
:=
TTaskPickerTaskContext
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
Result
.
taskId
:=
taskId
;
// Read-only lookup; an unknown task returns blank ids so the picker opens empty
q
:=
TUniQuery
.
Create
(
nil
);
try
q
.
Connection
:=
apiDB
.
ucETaskApi
;
q
.
SQL
.
Text
:=
'select t.PROJECT_ID, p.CUSTOMER_ID '
+
'from tasks t '
+
'join project p on p.PROJECT_ID = t.PROJECT_ID '
+
'where t.TASK_ID = :TASK_ID'
;
q
.
ParamByName
(
'TASK_ID'
).
AsString
:=
taskId
;
q
.
Open
;
if
not
q
.
Eof
then
begin
Result
.
projectId
:=
FieldText
(
q
,
'PROJECT_ID'
);
Result
.
customerId
:=
FieldText
(
q
,
'CUSTOMER_ID'
);
end
;
finally
q
.
Free
;
end
;
end
;
function
TTimeEntryService
.
DeleteTimeEntry
(
userId
:
string
;
entryId
:
Integer
):
Boolean
;
var
q
:
TUniQuery
;
...
...
@@ -890,6 +928,105 @@ begin
end
;
end
;
function
TTimeEntryService
.
AddTaskToFocus
(
userId
,
taskId
:
string
):
Boolean
;
var
q
:
TUniQuery
;
begin
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.AddTaskToFocus - USER_ID="%s" TASK_ID="%s"'
,
[
userId
,
taskId
]
));
q
:=
TUniQuery
.
Create
(
nil
);
try
q
.
Connection
:=
apiDB
.
ucETaskApi
;
q
.
Connection
.
StartTransaction
;
try
q
.
SQL
.
Text
:=
'select TASK_ID from task_assigned_user '
+
'where USER_ID = :USER_ID and TASK_ID = :TASK_ID'
;
q
.
ParamByName
(
'USER_ID'
).
AsString
:=
userId
;
q
.
ParamByName
(
'TASK_ID'
).
AsString
:=
taskId
;
q
.
Open
;
if
q
.
IsEmpty
then
begin
q
.
Close
;
q
.
SQL
.
Text
:=
'insert into task_assigned_user '
+
'(USER_ID, TASK_ID, FOCUSED_TASK) '
+
'values (:USER_ID, :TASK_ID, ''T'')'
;
end
else
begin
q
.
Close
;
q
.
SQL
.
Text
:=
'update task_assigned_user '
+
'set FOCUSED_TASK = ''T'' '
+
'where USER_ID = :USER_ID and TASK_ID = :TASK_ID'
;
end
;
q
.
ParamByName
(
'USER_ID'
).
AsString
:=
userId
;
q
.
ParamByName
(
'TASK_ID'
).
AsString
:=
taskId
;
q
.
ExecSQL
;
q
.
Connection
.
Commit
;
Result
:=
True
;
except
on
E
:
Exception
do
begin
if
q
.
Connection
.
InTransaction
then
q
.
Connection
.
Rollback
;
Logger
.
Log
(
2
,
'TimeEntryService.AddTaskToFocus - ERROR: '
+
E
.
Message
);
raise
;
end
;
end
;
finally
q
.
Free
;
end
;
end
;
function
TTimeEntryService
.
RemoveTaskFromFocus
(
userId
,
taskId
:
string
):
Boolean
;
var
q
:
TUniQuery
;
begin
Logger
.
Log
(
4
,
Format
(
'TimeEntryService.RemoveTaskFromFocus - USER_ID="%s" TASK_ID="%s"'
,
[
userId
,
taskId
]
));
// Only clears the flag; the task_assigned_user row is kept and never inserted here
q
:=
TUniQuery
.
Create
(
nil
);
try
q
.
Connection
:=
apiDB
.
ucETaskApi
;
q
.
Connection
.
StartTransaction
;
try
q
.
SQL
.
Text
:=
'update task_assigned_user '
+
'set FOCUSED_TASK = ''F'' '
+
'where USER_ID = :USER_ID and TASK_ID = :TASK_ID'
;
q
.
ParamByName
(
'USER_ID'
).
AsString
:=
userId
;
q
.
ParamByName
(
'TASK_ID'
).
AsString
:=
taskId
;
q
.
ExecSQL
;
q
.
Connection
.
Commit
;
Result
:=
True
;
except
on
E
:
Exception
do
begin
if
q
.
Connection
.
InTransaction
then
q
.
Connection
.
Rollback
;
Logger
.
Log
(
2
,
'TimeEntryService.RemoveTaskFromFocus - ERROR: '
+
E
.
Message
);
raise
;
end
;
end
;
finally
q
.
Free
;
end
;
end
;
initialization
RegisterServiceType
(
TTimeEntryService
);
...
...
emT3XDataServer/bin/emT3XDataServer.ini
View file @
e56dc151
[Settings]
MemoLogLevel
=
4
FileLogLevel
=
4
webClientVersion
=
0.9.
3
LogFileNum
=
2
45
MemoLogLevel
=
5
FileLogLevel
=
5
webClientVersion
=
0.9.
4.0
LogFileNum
=
2
18
[Database]
Server
=
192.168.102.133
--Server
=
192.168.116.131
--Server
=
192.168.102.131
--Server
=
192.168.116.128
--Server
=
192.168.102.131
--Server
=
192.168.159.10
Server
=
192.168.90.12
Database
=
eTask
Username
=
root
Password
=
emsys01
...
...
emT3XDataServer/emT3XDataServer.dproj
View file @
e56dc151
...
...
@@ -114,12 +114,11 @@
<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.9.
3.1
;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.
4.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>9</VerInfo_MinorVer>
<VerInfo_AutoIncVersion>true</VerInfo_AutoIncVersion>
<VerInfo_Build>1</VerInfo_Build>
<VerInfo_Release>3</VerInfo_Release>
<VerInfo_Release>4</VerInfo_Release>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment