Commit f6aacf29 by Mac Stephens

Add time items backend and client base functionality to load time entries and add new entry, WIP

parent 30fc30ba
...@@ -574,7 +574,7 @@ begin ...@@ -574,7 +574,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.AddTaskRow', [FTaskId, insertAfterItemNum] 'ITaskItemService.AddTaskRow', [FTaskId, insertAfterItemNum]
)); ));
console.log('AddTaskRow response=' + string(TJSJSON.stringify(response.Result))); console.log('AddTaskRow response=' + string(TJSJSON.stringify(response.Result)));
...@@ -661,13 +661,13 @@ var ...@@ -661,13 +661,13 @@ var
titleText: string; titleText: string;
rowCount: Integer; rowCount: Integer;
begin begin
console.log('IApiService.GetTaskItems called with task_id: ' + ATaskId); console.log('ITaskItemService.GetTaskItems called with task_id: ' + ATaskId);
console.log('Load Tasks Fired'); console.log('Load Tasks Fired');
Utils.ShowSpinner('spinner'); Utils.ShowSpinner('spinner');
try try
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.GetTaskItems', [ATaskId] 'ITaskItemService.GetTaskItems', [ATaskId]
)); ));
except except
on E: EXDataClientRequestException do on E: EXDataClientRequestException do
...@@ -1031,7 +1031,7 @@ begin ...@@ -1031,7 +1031,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.MoveTaskRow', 'ITaskItemService.MoveTaskRow',
[ [
StrToIntDef(xdwdsTaskstaskId.AsString, 0), StrToIntDef(xdwdsTaskstaskId.AsString, 0),
movedTaskItemId, movedTaskItemId,
...@@ -1106,7 +1106,7 @@ begin ...@@ -1106,7 +1106,7 @@ begin
Exit; Exit;
try try
response := await(xdwcTasks.RawInvokeAsync('IApiService.SaveTaskItemField', [payload])); response := await(xdwcTasks.RawInvokeAsync('ITaskItemService.SaveTaskItemField', [payload]));
console.log('SaveField: response=' + string(TJSJSON.stringify(response.Result))); console.log('SaveField: response=' + string(TJSJSON.stringify(response.Result)));
except except
on E: EXDataClientRequestException do on E: EXDataClientRequestException do
...@@ -1129,7 +1129,7 @@ begin ...@@ -1129,7 +1129,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.DeleteTaskRow', 'ITaskItemService.DeleteTaskRow',
[FSelectedTaskId, FSelectedTaskItemId] [FSelectedTaskId, FSelectedTaskItemId]
)); ));
console.log('DeleteTaskRow response=' + string(TJSJSON.stringify(response.Result))); console.log('DeleteTaskRow response=' + string(TJSJSON.stringify(response.Result)));
...@@ -1323,7 +1323,7 @@ begin ...@@ -1323,7 +1323,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.AddAssignedName', 'ITaskItemService.AddAssignedName',
[FTaskId, Trim(AName)] [FTaskId, Trim(AName)]
)); ));
...@@ -1349,7 +1349,7 @@ begin ...@@ -1349,7 +1349,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.RenameAssignedName', 'ITaskItemService.RenameAssignedName',
[FTaskId, Trim(AOldName), Trim(ANewName)] [FTaskId, Trim(AOldName), Trim(ANewName)]
)); ));
...@@ -1375,7 +1375,7 @@ begin ...@@ -1375,7 +1375,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.DeleteAssignedName', 'ITaskItemService.DeleteAssignedName',
[FTaskId, Trim(AName)] [FTaskId, Trim(AName)]
)); ));
...@@ -1500,7 +1500,7 @@ begin ...@@ -1500,7 +1500,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.AddApplicationName', 'ITaskItemService.AddApplicationName',
[FTaskId, Trim(AName)] [FTaskId, Trim(AName)]
)); ));
...@@ -1526,7 +1526,7 @@ begin ...@@ -1526,7 +1526,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.RenameApplicationName', 'ITaskItemService.RenameApplicationName',
[FTaskId, Trim(AOldName), Trim(ANewName)] [FTaskId, Trim(AOldName), Trim(ANewName)]
)); ));
...@@ -1552,7 +1552,7 @@ begin ...@@ -1552,7 +1552,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.DeleteApplicationName', 'ITaskItemService.DeleteApplicationName',
[FTaskId, Trim(AName)] [FTaskId, Trim(AName)]
)); ));
...@@ -1579,7 +1579,7 @@ begin ...@@ -1579,7 +1579,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.AddReportedName', 'ITaskItemService.AddReportedName',
[FTaskId, Trim(AName)] [FTaskId, Trim(AName)]
)); ));
...@@ -1605,7 +1605,7 @@ begin ...@@ -1605,7 +1605,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.RenameReportedName', 'ITaskItemService.RenameReportedName',
[FTaskId, Trim(AOldName), Trim(ANewName)] [FTaskId, Trim(AOldName), Trim(ANewName)]
)); ));
...@@ -1631,7 +1631,7 @@ begin ...@@ -1631,7 +1631,7 @@ begin
try try
response := await(xdwcTasks.RawInvokeAsync( response := await(xdwcTasks.RawInvokeAsync(
'IApiService.DeleteReportedName', 'ITaskItemService.DeleteReportedName',
[FTaskId, Trim(AName)] [FTaskId, Trim(AName)]
)); ));
......
...@@ -2,4 +2,100 @@ object FTimeEntries: TFTimeEntries ...@@ -2,4 +2,100 @@ object FTimeEntries: TFTimeEntries
Width = 640 Width = 640
Height = 480 Height = 480
ElementFont = efCSS ElementFont = efCSS
OnCreate = WebFormCreate
object edtWeekOf: TWebEdit
Left = 71
Top = 80
Width = 121
Height = 22
TabStop = False
ChildOrder = 1
ElementID = 'edt_week_of'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
ShowFocus = False
Text = 'edtWeekOf'
WidthPercent = 100.000000000000000000
OnChange = edtWeekOfChange
end
object edtStartDate: TWebEdit
Left = 245
Top = 80
Width = 121
Height = 22
TabStop = False
ChildOrder = 2
ElementID = 'edt_start_date'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
ShowFocus = False
Text = 'edtStartDate'
WidthPercent = 100.000000000000000000
OnChange = edtStartDateChange
end
object edtEndDate: TWebEdit
Left = 415
Top = 80
Width = 121
Height = 22
TabStop = False
ChildOrder = 3
ElementID = 'edt_end_date'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
ShowFocus = False
Text = 'edtEndDate'
WidthPercent = 100.000000000000000000
OnChange = edtEndDateChange
end
object btnAddEntry: TWebButton
Left = 440
Top = 24
Width = 96
Height = 25
Caption = 'Add Entry'
ChildOrder = 3
ElementID = 'btn_add_entry'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
TabStop = False
WidthPercent = 100.000000000000000000
OnClick = btnAddEntryClick
end
object xdwcTimeEntries: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 460
Top = 156
end
object xdwdsTimeEntries: TXDataWebDataSet
Left = 322
Top = 156
object xdwdsTimeEntriesentryId: TIntegerField
FieldName = 'entryId'
end
object xdwdsTimeEntriestaskDate: TStringField
FieldName = 'taskDate'
end
object xdwdsTimeEntriestaskId: TStringField
FieldName = 'taskId'
end
object xdwdsTimeEntriestaskDisplay: TStringField
FieldName = 'taskDisplay'
end
object xdwdsTimeEntrieshours: TFloatField
FieldName = 'hours'
end
object xdwdsTimeEntriestaskTime: TStringField
FieldName = 'taskTime'
end
object xdwdsTimeEntriescategory: TStringField
FieldName = 'category'
end
object xdwdsTimeEntriescategoryDesc: TStringField
FieldName = 'categoryDesc'
end
object xdwdsTimeEntriessummary: TStringField
FieldName = 'summary'
end
end
end end
<html> <div class="container-fluid p-2 d-flex flex-column h-100 overflow-hidden">
<head> <div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0 gap-3">
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <h5 class="mb-0 text-nowrap" id="lbl_time_entries_title"></h5>
<title>TMS Web Project</title>
<style> <div class="d-flex align-items-center gap-2 ms-auto flex-nowrap">
</style> <div id="lbl_time_total_rows" class="me-2 text-nowrap"></div>
</head>
<body> <div class="d-flex align-items-center gap-2 flex-nowrap">
</body> <label for="edt_week_of" class="form-label mb-0 text-nowrap small">Week of</label>
</html> <input id="edt_week_of" type="date" class="form-control form-control-sm time-date-picker">
\ No newline at end of file </div>
<div class="d-flex align-items-center gap-2 flex-nowrap">
<label for="edt_start_date" class="form-label mb-0 text-nowrap small">Start</label>
<input id="edt_start_date" type="date" class="form-control form-control-sm time-date-picker">
</div>
<div class="d-flex align-items-center gap-2 flex-nowrap">
<label for="edt_end_date" class="form-label mb-0 text-nowrap small">End</label>
<input id="edt_end_date" type="date" class="form-control form-control-sm time-date-picker">
</div>
<button id="btn_add_entry" class="btn btn-sm btn-success text-nowrap">Add Entry</button>
</div>
</div>
<div id="time_entries_table_host" class="flex-grow-1 min-h-0 overflow-auto"></div>
</div>
is-invalid .form-check-input { .is-invalid .form-check-input {
border: 1px solid #dc3545 !important; border: 1px solid #dc3545 !important;
} }
...@@ -7,14 +7,14 @@ is-invalid .form-check-input { ...@@ -7,14 +7,14 @@ is-invalid .form-check-input {
} }
.btn-primary { .btn-primary {
background-color: #286090 !important; background-color: #286090 !important;
border-color: #286090 !important; border-color: #286090 !important;
color: #fff !important; color: #fff !important;
} }
.btn-primary:hover { .btn-primary:hover {
background-color: #204d74 !important; background-color: #204d74 !important;
border-color: #204d74 !important; border-color: #204d74 !important;
} }
@keyframes slideInLeft { @keyframes slideInLeft {
...@@ -22,6 +22,7 @@ is-invalid .form-check-input { ...@@ -22,6 +22,7 @@ is-invalid .form-check-input {
transform: translateX(-120%); transform: translateX(-120%);
opacity: 0; opacity: 0;
} }
to { to {
transform: translateX(0); transform: translateX(0);
opacity: 1; opacity: 1;
...@@ -39,37 +40,6 @@ is-invalid .form-check-input { ...@@ -39,37 +40,6 @@ is-invalid .form-check-input {
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
/* This hides the up and down arrows on the item_num box, comment or remove it to add them back */
input[data-field="itemNum"]::-webkit-outer-spin-button,
input[data-field="itemNum"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[data-field="itemNum"] {
-moz-appearance: textfield;
appearance: textfield;
}
.tasks-vscroll {
height: 100%;
overflow: auto;
}
.tasks-vscroll thead th {
position: sticky;
top: 0;
z-index: 2;
background: var(--bs-body-bg);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tasks-vscroll thead th.th-resize {
z-index: 3;
}
span.card { span.card {
border: none; border: none;
...@@ -89,214 +59,6 @@ span.card { ...@@ -89,214 +59,6 @@ span.card {
user-select: none; user-select: none;
} }
.tasks-table {
table-layout: fixed;
}
.tasks-table th {
overflow: hidden;
}
.tasks-table td {
overflow: visible;
}
.nowrap-cell,
.wrap-cell {
overflow: visible;
}
.tasks-table .dropdown,
.task-dd-toggle,
.task-dd-label,
.cell-input,
.cell-textarea {
min-width: 0;
}
.dropdown-menu { .dropdown-menu {
z-index: 1055; z-index: 1055;
} }
.task-toolbar {
border: 1px solid var(--bs-border-color);
border-radius: 0.375rem;
background: var(--bs-body-bg);
white-space: nowrap;
}
.task-toolbar-btn {
min-width: 2rem;
padding: 0.25rem 0.5rem;
}
#lbl_total_rows {
white-space: nowrap;
}
.task-rich-editor {
white-space: pre-wrap;
overflow-wrap: anywhere;
height: auto;
}
.task-rich-editor:focus {
outline: 0;
}
.task-color-btn {
position: relative;
min-width: 2rem;
height: 2rem;
padding: 0.2rem 0.45rem;
}
.task-hidden-color-input {
position: absolute;
inset: 0;
opacity: 0;
pointer-events: none;
width: 1px;
height: 1px;
border: 0;
}
.task-color-glyph {
display: inline-block;
font-weight: 600;
line-height: 1;
}
.task-color-swatch {
position: absolute;
left: 0.35rem;
right: 0.35rem;
bottom: 0.2rem;
height: 0.18rem;
border-radius: 999px;
background: #000000;
}
.task-dd-toggle.status-cannot-duplicate {
--bs-btn-color: #41464b;
--bs-btn-bg: #e2e3e5;
--bs-btn-border-color: #c4c8cb;
--bs-btn-hover-color: #41464b;
--bs-btn-hover-bg: #d3d4d5;
--bs-btn-hover-border-color: #b9bcc0;
--bs-btn-active-color: #41464b;
--bs-btn-active-bg: #c6c7c8;
--bs-btn-active-border-color: #b9bcc0;
}
.task-dd-toggle.status-cannot-test {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-future-enhancement {
--bs-btn-color: #055160;
--bs-btn-bg: #cff4fc;
--bs-btn-border-color: #9eeaf9;
--bs-btn-hover-color: #055160;
--bs-btn-hover-bg: #b6effb;
--bs-btn-hover-border-color: #86e5f8;
--bs-btn-active-color: #055160;
--bs-btn-active-bg: #9eeaf9;
--bs-btn-active-border-color: #74dff6;
}
.task-dd-toggle.status-fixed-verified {
--bs-btn-color: #0f5132;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #0f5132;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #0f5132;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-fixed {
--bs-btn-color: #146c43;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #146c43;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #146c43;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-investigating {
--bs-btn-color: #664d03;
--bs-btn-bg: #fff3cd;
--bs-btn-border-color: #ffe69c;
--bs-btn-hover-color: #664d03;
--bs-btn-hover-bg: #ffecb5;
--bs-btn-hover-border-color: #ffdf7e;
--bs-btn-active-color: #664d03;
--bs-btn-active-bg: #ffe69c;
--bs-btn-active-border-color: #ffd966;
}
.task-dd-toggle.status-not-fixed {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-non-issue {
--bs-btn-color: #432874;
--bs-btn-bg: #e2d9f3;
--bs-btn-border-color: #cbbbe8;
--bs-btn-hover-color: #432874;
--bs-btn-hover-bg: #d6caee;
--bs-btn-hover-border-color: #bea9e2;
--bs-btn-active-color: #432874;
--bs-btn-active-bg: #cbbbe8;
--bs-btn-active-border-color: #b89ddd;
}
.task-dd-toggle.status-possibly-a-problem {
--bs-btn-color: #7a3e00;
--bs-btn-bg: #ffe5d0;
--bs-btn-border-color: #f7c79d;
--bs-btn-hover-color: #7a3e00;
--bs-btn-hover-bg: #ffd7b8;
--bs-btn-hover-border-color: #f2ba88;
--bs-btn-active-color: #7a3e00;
--bs-btn-active-bg: #f7c79d;
--bs-btn-active-border-color: #eeaf69;
}
.task-dd-toggle.status-default {
--bs-btn-color: #212529;
--bs-btn-bg: #f8f9fa;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #212529;
--bs-btn-hover-bg: #e9ecef;
--bs-btn-hover-border-color: #ced4da;
--bs-btn-active-color: #212529;
--bs-btn-active-bg: #dee2e6;
--bs-btn-active-border-color: #ced4da;
}
is-invalid .form-check-input {
border: 1px solid #dc3545 !important;
}
.is-invalid .form-check-label {
color: #dc3545 !important;
}
.btn-primary {
background-color: #286090 !important;
border-color: #286090 !important;
color: #fff !important;
}
.btn-primary:hover {
background-color: #204d74 !important;
border-color: #204d74 !important;
}
@keyframes slideInLeft {
from {
transform: translateX(-120%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.toast.slide-in {
animation: slideInLeft 0.4s ease-out forwards;
}
#spinner {
position: fixed !important;
z-index: 9999 !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* This hides the up and down arrows on the item_num box, comment or remove it to add them back */
input[data-field="itemNum"]::-webkit-outer-spin-button,
input[data-field="itemNum"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[data-field="itemNum"] {
-moz-appearance: textfield;
appearance: textfield;
}
.tasks-vscroll {
height: 100%;
overflow: auto;
}
.tasks-vscroll thead th {
position: sticky;
top: 0;
z-index: 2;
background: var(--bs-body-bg);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tasks-vscroll thead th.th-resize {
z-index: 3;
}
span.card {
border: none;
}
.th-resize {
position: relative;
}
.th-resize-handle {
position: absolute;
top: 0;
right: 0;
width: 8px;
height: 100%;
cursor: col-resize;
user-select: none;
}
.tasks-table {
table-layout: fixed;
}
.tasks-table th {
overflow: hidden;
}
.tasks-table td {
overflow: visible;
}
.nowrap-cell,
.wrap-cell {
overflow: visible;
}
.tasks-table .dropdown,
.task-dd-toggle,
.task-dd-label,
.cell-input,
.cell-textarea {
min-width: 0;
}
.dropdown-menu {
z-index: 1055;
}
.task-toolbar {
border: 1px solid var(--bs-border-color);
border-radius: 0.375rem;
background: var(--bs-body-bg);
white-space: nowrap;
}
.task-toolbar-btn {
min-width: 2rem;
padding: 0.25rem 0.5rem;
}
#lbl_total_rows {
white-space: nowrap;
}
.task-rich-editor {
white-space: pre-wrap;
overflow-wrap: anywhere;
height: auto;
}
.task-rich-editor:focus {
outline: 0;
}
.task-color-btn {
position: relative;
min-width: 2rem;
height: 2rem;
padding: 0.2rem 0.45rem;
}
.task-hidden-color-input {
position: absolute;
inset: 0;
opacity: 0;
pointer-events: none;
width: 1px;
height: 1px;
border: 0;
}
.task-color-glyph {
display: inline-block;
font-weight: 600;
line-height: 1;
}
.task-color-swatch {
position: absolute;
left: 0.35rem;
right: 0.35rem;
bottom: 0.2rem;
height: 0.18rem;
border-radius: 999px;
background: #000000;
}
.time-vscroll {
height: 100%;
overflow: auto;
}
.time-vscroll thead th {
position: sticky;
top: 0;
z-index: 2;
background: var(--bs-body-bg);
}
.time-vscroll thead th.th-resize {
z-index: 3;
}
.time-table {
table-layout: fixed;
}
.time-table th,
.time-table td {
overflow: hidden;
}
.time-table .dropdown,
.time-dd-toggle,
.time-dd-label,
.cell-input,
.cell-textarea {
min-width: 0;
}
.task-dd-toggle.status-cannot-duplicate {
--bs-btn-color: #41464b;
--bs-btn-bg: #e2e3e5;
--bs-btn-border-color: #c4c8cb;
--bs-btn-hover-color: #41464b;
--bs-btn-hover-bg: #d3d4d5;
--bs-btn-hover-border-color: #b9bcc0;
--bs-btn-active-color: #41464b;
--bs-btn-active-bg: #c6c7c8;
--bs-btn-active-border-color: #b9bcc0;
}
.task-dd-toggle.status-cannot-test {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-future-enhancement {
--bs-btn-color: #055160;
--bs-btn-bg: #cff4fc;
--bs-btn-border-color: #9eeaf9;
--bs-btn-hover-color: #055160;
--bs-btn-hover-bg: #b6effb;
--bs-btn-hover-border-color: #86e5f8;
--bs-btn-active-color: #055160;
--bs-btn-active-bg: #9eeaf9;
--bs-btn-active-border-color: #74dff6;
}
.task-dd-toggle.status-fixed-verified {
--bs-btn-color: #0f5132;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #0f5132;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #0f5132;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-fixed {
--bs-btn-color: #146c43;
--bs-btn-bg: #d1e7dd;
--bs-btn-border-color: #a3cfbb;
--bs-btn-hover-color: #146c43;
--bs-btn-hover-bg: #badbcc;
--bs-btn-hover-border-color: #8fc5a9;
--bs-btn-active-color: #146c43;
--bs-btn-active-bg: #a3cfbb;
--bs-btn-active-border-color: #7db89d;
}
.task-dd-toggle.status-investigating {
--bs-btn-color: #664d03;
--bs-btn-bg: #fff3cd;
--bs-btn-border-color: #ffe69c;
--bs-btn-hover-color: #664d03;
--bs-btn-hover-bg: #ffecb5;
--bs-btn-hover-border-color: #ffdf7e;
--bs-btn-active-color: #664d03;
--bs-btn-active-bg: #ffe69c;
--bs-btn-active-border-color: #ffd966;
}
.task-dd-toggle.status-not-fixed {
--bs-btn-color: #842029;
--bs-btn-bg: #f8d7da;
--bs-btn-border-color: #f1aeb5;
--bs-btn-hover-color: #842029;
--bs-btn-hover-bg: #f1c2c7;
--bs-btn-hover-border-color: #ea9ca6;
--bs-btn-active-color: #842029;
--bs-btn-active-bg: #eaadb5;
--bs-btn-active-border-color: #e68592;
}
.task-dd-toggle.status-non-issue {
--bs-btn-color: #432874;
--bs-btn-bg: #e2d9f3;
--bs-btn-border-color: #cbbbe8;
--bs-btn-hover-color: #432874;
--bs-btn-hover-bg: #d6caee;
--bs-btn-hover-border-color: #bea9e2;
--bs-btn-active-color: #432874;
--bs-btn-active-bg: #cbbbe8;
--bs-btn-active-border-color: #b89ddd;
}
.task-dd-toggle.status-possibly-a-problem {
--bs-btn-color: #7a3e00;
--bs-btn-bg: #ffe5d0;
--bs-btn-border-color: #f7c79d;
--bs-btn-hover-color: #7a3e00;
--bs-btn-hover-bg: #ffd7b8;
--bs-btn-hover-border-color: #f2ba88;
--bs-btn-active-color: #7a3e00;
--bs-btn-active-bg: #f7c79d;
--bs-btn-active-border-color: #eeaf69;
}
.task-dd-toggle.status-default {
--bs-btn-color: #212529;
--bs-btn-bg: #f8f9fa;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #212529;
--bs-btn-hover-bg: #e9ecef;
--bs-btn-hover-border-color: #ced4da;
--bs-btn-active-color: #212529;
--bs-btn-active-bg: #dee2e6;
--bs-btn-active-border-color: #ced4da;
}
.time-vscroll {
height: 100%;
overflow: auto;
}
.time-vscroll thead th {
position: sticky;
top: 0;
z-index: 2;
background: var(--bs-body-bg);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.time-vscroll thead th.th-resize {
z-index: 3;
}
.time-table {
table-layout: fixed;
}
.time-table th {
overflow: hidden;
}
.time-table td {
overflow: visible;
}
.time-table .nowrap-cell,
.time-table .wrap-cell {
overflow: visible;
}
.time-table .dropdown,
.time-dd-toggle,
.time-dd-label,
.cell-input,
.cell-textarea {
min-width: 0;
}
.time-table .dropdown-menu {
z-index: 1055;
}
\ No newline at end of file
...@@ -84,6 +84,12 @@ begin ...@@ -84,6 +84,12 @@ begin
if SameText(timeEntriesParam, 'true') then if SameText(timeEntriesParam, 'true') then
begin begin
if AuthService.Authenticated and not AuthService.TokenExpired then
begin
DisplayMainView;
Exit;
end;
DisplayLoginView('', '', ''); DisplayLoginView('', '', '');
Exit; Exit;
end; end;
......
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
<VerInfo_MajorVer>0</VerInfo_MajorVer> <VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>8</VerInfo_MinorVer> <VerInfo_MinorVer>8</VerInfo_MinorVer>
<VerInfo_Release>9</VerInfo_Release> <VerInfo_Release>9</VerInfo_Release>
<TMSURLParams>?user_id=1019&amp;task_id=4045&amp;url_code=123456</TMSURLParams> <TMSURLParams>?time_entries=true&amp;date=2026-05-01</TMSURLParams>
<TMSWebBrowser>1</TMSWebBrowser> <TMSWebBrowser>1</TMSWebBrowser>
<TMSWebSingleInstance>1</TMSWebSingleInstance> <TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSUseJSDebugger>2</TMSUseJSDebugger> <TMSUseJSDebugger>2</TMSUseJSDebugger>
...@@ -160,6 +160,8 @@ ...@@ -160,6 +160,8 @@
<None Include="css\app.css"/> <None Include="css\app.css"/>
<None Include="config\config.json"/> <None Include="config\config.json"/>
<None Include="css\spinner.css"/> <None Include="css\spinner.css"/>
<None Include="css\task-items.css"/>
<None Include="css\time-entries.css"/>
<BuildConfiguration Include="Base"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>
...@@ -240,6 +242,18 @@ ...@@ -240,6 +242,18 @@
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
</Platform> </Platform>
</DeployFile> </DeployFile>
<DeployFile LocalName="css\task-items.css" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="css\time-entries.css" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile"/> <DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile"> <DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32"> <Platform Name="Win32">
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="css/app.css" rel="stylesheet"/> <link href="css/app.css" rel="stylesheet"/>
<link href="css/task-items.css" rel="stylesheet"/>
<link href="css/time-entries.css" rel="stylesheet"/>
<link href="css/spinner.css" rel="stylesheet"/> <link href="css/spinner.css" rel="stylesheet"/>
<script crossorigin="anonymous" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" src="https://code.jquery.com/jquery-3.7.1.js"></script> <script crossorigin="anonymous" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" src="https://code.jquery.com/jquery-3.7.1.js"></script>
...@@ -17,7 +19,7 @@ ...@@ -17,7 +19,7 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script src="$(ProjectName).js"></script> <script src="$(ProjectName).js"></script>
</head> <link href="css/task-items.css" rel="stylesheet"/><link href="css/time-entries.css" rel="stylesheet"/></head>
<body> <body>
<noscript>Your browser does not support JavaScript!</noscript> <noscript>Your browser does not support JavaScript!</noscript>
<script>rtl.run();</script> <script>rtl.run();</script>
...@@ -27,3 +29,5 @@ ...@@ -27,3 +29,5 @@
object ApiDatabase: TApiDatabase object ApiDatabase: TApiDatabase
OnCreate = DataModuleCreate OnCreate = DataModuleCreate
Height = 475 Height = 632
Width = 996 Width = 994
object ucETaskApi: TUniConnection object ucETaskApi: TUniConnection
AutoCommit = False AutoCommit = False
ProviderName = 'MySQL' ProviderName = 'MySQL'
Database = 'eTask' Database = 'eTask'
LoginPrompt = False LoginPrompt = False
Left = 267 Left = 435
Top = 395 Top = 359
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF' EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end end
object MySQLUniProvider1: TMySQLUniProvider object MySQLUniProvider1: TMySQLUniProvider
Left = 416 Left = 546
Top = 398 Top = 356
end end
object uqUsers: TUniQuery object uqUsers: TUniQuery
Connection = ucETaskApi Connection = ucETaskApi
...@@ -142,7 +142,7 @@ object ApiDatabase: TApiDatabase ...@@ -142,7 +142,7 @@ object ApiDatabase: TApiDatabase
'from task_items' 'from task_items'
'where TASK_ID = :TASK_ID' 'where TASK_ID = :TASK_ID'
'order by ITEM_NUM') 'order by ITEM_NUM')
Left = 56 Left = 58
Top = 26 Top = 26
ParamData = < ParamData = <
item item
...@@ -282,7 +282,7 @@ object ApiDatabase: TApiDatabase ...@@ -282,7 +282,7 @@ object ApiDatabase: TApiDatabase
'left join project p' 'left join project p'
' on p.PROJECT_ID = t.PROJECT_ID' ' on p.PROJECT_ID = t.PROJECT_ID'
'where t.TASK_ID = :TASK_ID') 'where t.TASK_ID = :TASK_ID')
Left = 54 Left = 60
Top = 82 Top = 82
ParamData = < ParamData = <
item item
...@@ -367,7 +367,7 @@ object ApiDatabase: TApiDatabase ...@@ -367,7 +367,7 @@ object ApiDatabase: TApiDatabase
'from task_items' 'from task_items'
'where TASK_ITEM_ID = :TASK_ITEM_ID' 'where TASK_ITEM_ID = :TASK_ITEM_ID'
' and TASK_ID = :TASK_ID') ' and TASK_ID = :TASK_ID')
Left = 240 Left = 236
Top = 16 Top = 16
ParamData = < ParamData = <
item item
...@@ -1085,4 +1085,61 @@ object ApiDatabase: TApiDatabase ...@@ -1085,4 +1085,61 @@ object ApiDatabase: TApiDatabase
Value = nil Value = nil
end> end>
end end
object uqAddTimeEntry: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'insert into time_items ('
' ENTRY_ID,'
' USER_ID,'
' TASK_DATE,'
' HOURS,'
' TASK_TIME,'
' CATEGORY,'
' SUMMARY,'
' CREATE_DATE,'
' CREATED_BY,'
' MODIFY_DATE,'
' MODIFIED_BY'
') values ('
' :ENTRY_ID,'
' :USER_ID,'
' :TASK_DATE,'
' null,'
' null,'
' null,'
' null,'
' now(),'
' :CREATED_BY,'
' now(),'
' :MODIFIED_BY'
')')
Left = 436
Top = 426
ParamData = <
item
DataType = ftUnknown
Name = 'ENTRY_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'USER_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'TASK_DATE'
Value = nil
end
item
DataType = ftUnknown
Name = 'CREATED_BY'
Value = nil
end
item
DataType = ftUnknown
Name = 'MODIFIED_BY'
Value = nil
end>
end
end end
...@@ -88,6 +88,7 @@ type ...@@ -88,6 +88,7 @@ type
uqRenameTaskItemReportedBy: TUniQuery; uqRenameTaskItemReportedBy: TUniQuery;
uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField; uqProjectReportedUsersTASK_ITEM_USER_ID: TStringField;
uqProjectReportedUsersNAME: TStringField; uqProjectReportedUsersNAME: TStringField;
uqAddTimeEntry: TUniQuery;
procedure DataModuleCreate(Sender: TObject); procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet); procedure uqUsersCalcFields(DataSet: TDataSet);
private private
......
...@@ -53,7 +53,7 @@ uses ...@@ -53,7 +53,7 @@ uses
XData.Sys.Exceptions, XData.Sys.Exceptions,
Common.Logging, Common.Logging,
Common.Middleware.Logging, Common.Middleware.Logging,
Common.Config, Vcl.Forms, IniFiles, Api.Service; Common.Config, Vcl.Forms, IniFiles, TaskItem.Service, TimeEntry.Service;
{%CLASSGROUP 'Vcl.Controls.TControl'} {%CLASSGROUP 'Vcl.Controls.TControl'}
......
...@@ -299,7 +299,7 @@ begin ...@@ -299,7 +299,7 @@ begin
try try
jwt.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36)); jwt.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
jwt.Claims.IssuedAt := Now; jwt.Claims.IssuedAt := Now;
jwt.Claims.Expiration := IncHour(Now, 12); jwt.Claims.Expiration := IncHour(Now, 24);
jwt.Claims.SetClaimOfType<string>('user_id', Self.userId); jwt.Claims.SetClaimOfType<string>('user_id', Self.userId);
jwt.Claims.SetClaimOfType<string>('user_name', Self.userName); jwt.Claims.SetClaimOfType<string>('user_name', Self.userName);
......
unit Main; unit Main;
//Authors:
//Elias Sarraf
//Mac Stephens
//Cameron Hayes
interface interface
uses uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Winapi.ShellApi, Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Winapi.ShellApi,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections, System.IniFiles, Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections, System.IniFiles,
Auth.Service, Auth.Server.Module, Api.Service, Api.Server.Module, App.Server.Module, TaskItem.Service, Auth.Server.Module, Auth.Service, Api.Server.Module, App.Server.Module,
ExeInfo; TimeEntry.Service, TimeEntry.ServiceImpl, ExeInfo;
type type
TFMain = class(TForm) TFMain = class(TForm)
......
unit Api.Service; unit TaskItem.Service;
interface interface
...@@ -115,10 +115,10 @@ type ...@@ -115,10 +115,10 @@ type
type type
[ServiceContract, Model(API_MODEL)] [ServiceContract, Model(API_MODEL)]
IApiService = interface(IInvokable) ITaskItemService = interface(IInvokable)
['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}'] ['{0EFB33D7-8C4C-4F3C-9BC3-8B4D444B5F69}']
function GetTaskItems(taskId: string): TTaskItemsResponse; [HttpGet] function GetTaskItems(taskId: string): TTaskItemsResponse;
[HttpPost] function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean; [HttpPost] function AddTaskRow(taskId: string; insertAfterItemNum: Integer): Boolean;
[HttpPost] function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse; [HttpPost] function AddAssignedName(taskId: string; name: string): TTaskUserOptionsResponse;
...@@ -134,9 +134,10 @@ type ...@@ -134,9 +134,10 @@ type
[HttpPost] function RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse; [HttpPost] function RenameApplicationName(taskId: string; oldName: string; newName: string): TTaskApplicationOptionsResponse;
[HttpPost] function DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse; [HttpPost] function DeleteApplicationName(taskId: string; name: string): TTaskApplicationOptionsResponse;
procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer); [HttpPost] procedure MoveTaskRow(const taskId: Integer; const taskItemId: Integer; const newItemNum: Integer);
function DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean; [HttpPost] function DeleteTaskRow(const taskId: Integer; const taskItemId: Integer): Boolean;
function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean; [HttpPost] function SaveTaskItemField(const Item: TTaskItemFieldSave): Boolean;
end; end;
implementation implementation
...@@ -188,6 +189,6 @@ begin ...@@ -188,6 +189,6 @@ begin
end; end;
initialization initialization
RegisterServiceType(TypeInfo(IApiService)); RegisterServiceType(TypeInfo(ITaskItemService));
end. end.
unit TimeEntry.Service;
interface
uses
System.Generics.Collections,
XData.Service.Common,
Bcl.Types.Nullable,
Aurelius.Mapping.Attributes;
const
API_MODEL = 'Api';
type
TTimeEntry = class
public
entryId: Integer;
taskDate: string;
taskId: string;
taskDisplay: string;
hours: Nullable<Double>;
taskTime: string;
category: string;
categoryDesc: string;
summary: string;
end;
TTimeEntryTaskOption = class
public
taskId: string;
taskDisplay: string;
end;
TTimeEntryCategoryOption = class
public
code: string;
codeDesc: string;
end;
TTimeEntriesResponse = class
public
userName: string;
count: Integer;
items: TList<TTimeEntry>;
taskOptions: TList<TTimeEntryTaskOption>;
categoryOptions: TList<TTimeEntryCategoryOption>;
constructor Create;
destructor Destroy; override;
end;
[ServiceContract, Model(API_MODEL)]
ITimeEntryService = interface(IInvokable)
['{B18BCD1E-B19A-4D25-BBA9-50A24FC4C690}']
[HttpGet] function GetTimeEntries(userId, startDate, endDate: string): TTimeEntriesResponse;
[HttpPost] function AddTimeEntry(userId, taskDate: string): string;
end;
implementation
constructor TTimeEntriesResponse.Create;
begin
inherited;
items := TList<TTimeEntry>.Create;
taskOptions := TList<TTimeEntryTaskOption>.Create;
categoryOptions := TList<TTimeEntryCategoryOption>.Create;
end;
destructor TTimeEntriesResponse.Destroy;
begin
items.Free;
taskOptions.Free;
categoryOptions.Free;
inherited;
end;
initialization
RegisterServiceType(TypeInfo(ITimeEntryService));
end.
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
MemoLogLevel=4 MemoLogLevel=4
FileLogLevel=4 FileLogLevel=4
webClientVersion=0.8.9 webClientVersion=0.8.9
LogFileNum=190 LogFileNum=219
[Database] [Database]
Server=192.168.102.131 Server=192.168.102.131
......
...@@ -20,9 +20,11 @@ uses ...@@ -20,9 +20,11 @@ uses
Auth.Service in 'Source\Auth.Service.pas', Auth.Service in 'Source\Auth.Service.pas',
Auth.ServiceImpl in 'Source\Auth.ServiceImpl.pas', Auth.ServiceImpl in 'Source\Auth.ServiceImpl.pas',
App.Server.Module in 'Source\App.Server.Module.pas' {AppServerModule: TDataModule}, App.Server.Module in 'Source\App.Server.Module.pas' {AppServerModule: TDataModule},
Api.Service in 'Source\Api.Service.pas', TaskItem.Service in 'Source\TaskItem.Service.pas',
Api.ServiceImpl in 'Source\Api.ServiceImpl.pas', TaskItem.ServiceImpl in 'Source\TaskItem.ServiceImpl.pas',
Common.Ini in 'Source\Common.Ini.pas'; Common.Ini in 'Source\Common.Ini.pas',
TimeEntry.Service in 'Source\TimeEntry.Service.pas',
TimeEntry.ServiceImpl in 'Source\TimeEntry.ServiceImpl.pas';
type type
TMemoLogAppender = class( TInterfacedObject, ILogAppender ) TMemoLogAppender = class( TInterfacedObject, ILogAppender )
......
...@@ -175,9 +175,11 @@ ...@@ -175,9 +175,11 @@
<Form>AppServerModule</Form> <Form>AppServerModule</Form>
<DesignClass>TDataModule</DesignClass> <DesignClass>TDataModule</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="Source\Api.Service.pas"/> <DCCReference Include="Source\TaskItem.Service.pas"/>
<DCCReference Include="Source\Api.ServiceImpl.pas"/> <DCCReference Include="Source\TaskItem.ServiceImpl.pas"/>
<DCCReference Include="Source\Common.Ini.pas"/> <DCCReference Include="Source\Common.Ini.pas"/>
<DCCReference Include="Source\TimeEntry.Service.pas"/>
<DCCReference Include="Source\TimeEntry.ServiceImpl.pas"/>
<BuildConfiguration Include="Base"> <BuildConfiguration Include="Base">
<Key>Base</Key> <Key>Base</Key>
</BuildConfiguration> </BuildConfiguration>
......
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