Commit 7a8e3164 by Mac Stephens

Add field-level time entry saving, focused task dropdowns, place support, and delete entry flow

parent 29263ec6
...@@ -160,6 +160,12 @@ begin ...@@ -160,6 +160,12 @@ begin
var btnRight = document.getElementById('btn_confirm_right'); var btnRight = document.getElementById('btn_confirm_right');
var bsModal; var bsModal;
console.log('confirmation modal=', modal);
console.log('confirmation body=', body);
console.log('confirmation left=', btnLeft);
console.log('confirmation right=', btnRight);
if (body) body.innerText = msg; if (body) body.innerText = msg;
if (btnLeft) btnLeft.innerText = leftLabel; if (btnLeft) btnLeft.innerText = leftLabel;
if (btnRight) btnRight.innerText = rightLabel; if (btnRight) btnRight.innerText = rightLabel;
......
...@@ -86,24 +86,28 @@ ...@@ -86,24 +86,28 @@
</div> </div>
</div> </div>
<!-- Confirmation Modal --> <!-- Confirmation Modal -->
<div class="modal fade" id="mdl_confirmation" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="main_confirmation_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content shadow-lg"> <div class="modal-content shadow-lg">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Confirm</h5> <h5 class="modal-title">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body fw-bold" id="lbl_confirmation_body"> <div class="modal-body fw-bold" id="main_modal_body">
Placeholder text Placeholder text
</div> </div>
<div class="modal-footer justify-content-center"> <div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary me-3" id="btn_confirm_left">Cancel</button> <button type="button" class="btn btn-danger me-3" id="btn_confirm_left">
<button type="button" class="btn btn-secondary" id="btn_confirm_right">Confirm</button> Delete
</button>
<button type="button" class="btn btn-secondary" id="btn_confirm_right">
Cancel
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Notification Modal --> <!-- Notification Modal -->
<div class="modal fade" id="mdl_notification" tabindex="-1" aria-labelledby="lbl_notification_title" <div class="modal fade" id="mdl_notification" tabindex="-1" aria-labelledby="lbl_notification_title"
......
object FTimeEntries: TFTimeEntries object FTimeEntries: TFTimeEntries
Width = 640 Width = 640
Height = 480 Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS ElementFont = efCSS
OnCreate = WebFormCreate OnCreate = WebFormCreate
object lblValidationMessage: TWebLabel object lblValidationMessage: TWebLabel
Left = 90 Left = 90
Top = 32 Top = 32
Width = 69 Width = 3
Height = 15 Height = 15
ElementID = 'lbl_validation_message' ElementID = 'lbl_validation_message'
ElementFont = efCSS ElementFont = efCSS
...@@ -26,7 +27,7 @@ object FTimeEntries: TFTimeEntries ...@@ -26,7 +27,7 @@ object FTimeEntries: TFTimeEntries
ShowFocus = False ShowFocus = False
Text = 'edtWeekOf' Text = 'edtWeekOf'
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
OnChange = edtWeekOfChange OnExit = edtWeekOfExit
end end
object edtStartDate: TWebEdit object edtStartDate: TWebEdit
Left = 245 Left = 245
...@@ -41,7 +42,6 @@ object FTimeEntries: TFTimeEntries ...@@ -41,7 +42,6 @@ object FTimeEntries: TFTimeEntries
ShowFocus = False ShowFocus = False
Text = 'edtStartDate' Text = 'edtStartDate'
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
OnChange = edtStartDateChange
end end
object edtEndDate: TWebEdit object edtEndDate: TWebEdit
Left = 415 Left = 415
...@@ -56,7 +56,6 @@ object FTimeEntries: TFTimeEntries ...@@ -56,7 +56,6 @@ object FTimeEntries: TFTimeEntries
ShowFocus = False ShowFocus = False
Text = 'edtEndDate' Text = 'edtEndDate'
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
OnChange = edtEndDateChange
end end
object btnAddEntry: TWebButton object btnAddEntry: TWebButton
Left = 440 Left = 440
...@@ -72,6 +71,33 @@ object FTimeEntries: TFTimeEntries ...@@ -72,6 +71,33 @@ object FTimeEntries: TFTimeEntries
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
OnClick = btnAddEntryClick OnClick = btnAddEntryClick
end end
object btnDeleteEntry: TWebButton
Left = 338
Top = 24
Width = 96
Height = 25
Caption = 'Delete Entry'
ChildOrder = 5
ElementID = 'btn_delete_entry'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnDeleteEntryClick
end
object btnSearchRange: TWebButton
Left = 236
Top = 24
Width = 96
Height = 25
Caption = 'Search Range'
ChildOrder = 6
ElementID = 'btn_search_range'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnSearchRangeClick
end
object xdwcTimeEntries: TXDataWebClient object xdwcTimeEntries: TXDataWebClient
Connection = DMConnection.ApiConnection Connection = DMConnection.ApiConnection
Left = 460 Left = 460
...@@ -107,5 +133,11 @@ object FTimeEntries: TFTimeEntries ...@@ -107,5 +133,11 @@ object FTimeEntries: TFTimeEntries
object xdwdsTimeEntriessummary: TStringField object xdwdsTimeEntriessummary: TStringField
FieldName = 'summary' FieldName = 'summary'
end end
object xdwdsTimeEntriesplace: TStringField
FieldName = 'place'
end
object xdwdsTimeEntriesplaceDesc: TStringField
FieldName = 'placeDesc'
end
end end
end end
...@@ -7,22 +7,74 @@ ...@@ -7,22 +7,74 @@
<div class="d-flex align-items-center gap-2 flex-nowrap"> <div class="d-flex align-items-center gap-2 flex-nowrap">
<label for="edt_week_of" class="form-label mb-0 text-nowrap small">Week of</label> <label for="edt_week_of" class="form-label mb-0 text-nowrap small">Week of</label>
<input id="edt_week_of" type="date" class="form-control form-control-sm time-date-picker"> <input id="edt_week_of" type="date" class="form-control form-control-sm time-date-picker" />
</div> </div>
<div class="d-flex align-items-center gap-2 flex-nowrap"> <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> <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"> <input id="edt_start_date" type="date" class="form-control form-control-sm time-date-picker" />
</div> </div>
<div class="d-flex align-items-center gap-2 flex-nowrap"> <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> <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"> <input id="edt_end_date" type="date" class="form-control form-control-sm time-date-picker" />
</div> </div>
<button id="btn_add_entry" class="btn btn-sm btn-success text-nowrap">Add Entry</button> <button id="btn_search_range" class="btn btn-sm btn-primary text-nowrap">
Search Range
</button>
<button id="btn_add_entry" class="btn btn-sm btn-success text-nowrap">
Add Entry
</button>
<button id="btn_delete_entry" class="btn btn-sm btn-danger text-nowrap" disabled>
Delete Entry
</button>
</div> </div>
</div> </div>
<div id="lbl_validation_message" class="alert alert-danger py-1 px-2 mb-2 d-none small"></div> <div id="lbl_validation_message" class="alert alert-danger py-1 px-2 mb-2 d-none small"></div>
<div id="time_entries_table_host" class="flex-grow-1 min-h-0 overflow-auto"></div> <div id="time_entries_table_host" class="flex-grow-1 min-h-0 overflow-auto"></div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="task_picker_offcanvas" aria-labelledby="task_picker_title">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="task_picker_title">Find Task</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="mb-3">
<label for="task_picker_customer" class="form-label">Customer</label>
<select id="task_picker_customer" class="form-select form-select-sm">
<option value=""></option>
</select>
</div>
<div class="mb-3">
<label for="task_picker_project" class="form-label">Project</label>
<select id="task_picker_project" class="form-select form-select-sm" disabled>
<option value=""></option>
</select>
</div>
<div class="mb-3">
<label for="task_picker_task" class="form-label">Task</label>
<select id="task_picker_task" class="form-select form-select-sm" disabled>
<option value=""></option>
</select>
</div>
<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">
<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>
</div>
</div>
</div>
</div> </div>
...@@ -17,7 +17,8 @@ uses ...@@ -17,7 +17,8 @@ uses
uDropdownHelpers in 'uDropdownHelpers.pas', uDropdownHelpers in 'uDropdownHelpers.pas',
View.Login in 'View.Login.pas' {FViewLogin: TWebForm} {*.html}, View.Login in 'View.Login.pas' {FViewLogin: TWebForm} {*.html},
View.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html}, View.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html},
View.TimeEntries in 'View.TimeEntries.pas' {FTimeEntries: TWebForm} {*.html}; View.TimeEntries in 'View.TimeEntries.pas' {FTimeEntries: TWebForm} {*.html},
uTaskPickerOffCanvas in 'uTaskPickerOffCanvas.pas';
{$R *.res} {$R *.res}
......
...@@ -156,6 +156,7 @@ ...@@ -156,6 +156,7 @@
<Form>FTimeEntries</Form> <Form>FTimeEntries</Form>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="uTaskPickerOffCanvas.pas"/>
<None Include="index.html"/> <None Include="index.html"/>
<None Include="css\app.css"/> <None Include="css\app.css"/>
<None Include="config\config.json"/> <None Include="config\config.json"/>
......
unit uTaskPickerOffCanvas;
interface
uses
System.SysUtils, JS, Web, XData.Web.Client;
type
TTaskPickedProc = reference to procedure(ARowIndex: Integer; const ATaskId, ATaskDisplay: string);
TTaskPickerOffCanvas = class
private
FClient: TXDataWebClient;
FUserId: string;
FRowIndex: Integer;
FOnTaskPicked: TTaskPickedProc;
FCustomers: TJSArray;
FProjects: TJSArray;
FTasks: TJSArray;
procedure BindEvents;
procedure ClearSelect(const AElementId: string; ADisabled: Boolean);
procedure FillSelect(const AElementId: string; AItems: TJSArray);
function SelectedValue(const AElementId: string): string;
function FindDisplay(AItems: TJSArray; const AValue: string): string;
procedure SetStatus(const AMessage: string);
procedure ClearStatus;
procedure SetSaveEnabled(AEnabled: Boolean);
procedure Show;
procedure Hide;
[async] procedure LoadCustomers;
[async] procedure LoadProjects(const ACustomerId: string);
[async] procedure LoadTasks(const AProjectId: string);
[async] procedure CustomerChanged(Event: TJSEvent);
[async] procedure ProjectChanged(Event: TJSEvent);
procedure TaskChanged(Event: TJSEvent);
procedure SaveClicked(Event: TJSEvent);
public
constructor Create(AClient: TXDataWebClient; AOnTaskPicked: TTaskPickedProc);
[async] procedure Open(const AUserId: string; ARowIndex: Integer);
end;
implementation
constructor TTaskPickerOffCanvas.Create(AClient: TXDataWebClient; AOnTaskPicked: TTaskPickedProc);
begin
inherited Create;
FClient := AClient;
FOnTaskPicked := AOnTaskPicked;
FCustomers := TJSArray.new;
FProjects := TJSArray.new;
FTasks := TJSArray.new;
FRowIndex := -1;
BindEvents;
end;
procedure TTaskPickerOffCanvas.BindEvents;
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(document.getElementById('task_picker_customer'));
if Assigned(el) then
el.addEventListener('change', TJSEventHandler(@CustomerChanged));
el := TJSHTMLElement(document.getElementById('task_picker_project'));
if Assigned(el) then
el.addEventListener('change', TJSEventHandler(@ProjectChanged));
el := TJSHTMLElement(document.getElementById('task_picker_task'));
if Assigned(el) then
el.addEventListener('change', TJSEventHandler(@TaskChanged));
el := TJSHTMLElement(document.getElementById('btn_task_picker_save'));
if Assigned(el) then
el.addEventListener('click', TJSEventHandler(@SaveClicked));
end;
procedure TTaskPickerOffCanvas.ClearSelect(const AElementId: string; ADisabled: Boolean);
var
selectEl: TJSHTMLSelectElement;
begin
selectEl := TJSHTMLSelectElement(document.getElementById(AElementId));
if not Assigned(selectEl) then
Exit;
selectEl.innerHTML := '<option value=""></option>';
selectEl.disabled := ADisabled;
end;
procedure TTaskPickerOffCanvas.FillSelect(const AElementId: string; AItems: TJSArray);
var
selectEl: TJSHTMLSelectElement;
optionEl: TJSHTMLOptionElement;
itemObj: TJSObject;
i: Integer;
begin
selectEl := TJSHTMLSelectElement(document.getElementById(AElementId));
if not Assigned(selectEl) then
Exit;
selectEl.innerHTML := '<option value=""></option>';
for i := 0 to AItems.length - 1 do
begin
itemObj := TJSObject(AItems[i]);
optionEl := TJSHTMLOptionElement(document.createElement('option'));
optionEl.value := string(itemObj['value']);
optionEl.text := string(itemObj['display']);
selectEl.appendChild(optionEl);
end;
selectEl.disabled := False;
end;
function TTaskPickerOffCanvas.SelectedValue(const AElementId: string): string;
var
selectEl: TJSHTMLSelectElement;
begin
Result := '';
selectEl := TJSHTMLSelectElement(document.getElementById(AElementId));
if Assigned(selectEl) then
Result := string(selectEl.value);
end;
function TTaskPickerOffCanvas.FindDisplay(AItems: TJSArray; const AValue: string): string;
var
itemObj: TJSObject;
i: Integer;
begin
Result := '';
for i := 0 to AItems.length - 1 do
begin
itemObj := TJSObject(AItems[i]);
if string(itemObj['value']) = AValue then
Exit(string(itemObj['display']));
end;
end;
procedure TTaskPickerOffCanvas.SetStatus(const AMessage: string);
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(document.getElementById('task_picker_status'));
if not Assigned(el) then
Exit;
el.innerText := AMessage;
el.classList.remove('d-none');
end;
procedure TTaskPickerOffCanvas.ClearStatus;
var
el: TJSHTMLElement;
begin
el := TJSHTMLElement(document.getElementById('task_picker_status'));
if not Assigned(el) then
Exit;
el.innerText := '';
el.classList.add('d-none');
end;
procedure TTaskPickerOffCanvas.SetSaveEnabled(AEnabled: Boolean);
var
btn: TJSHTMLButtonElement;
begin
btn := TJSHTMLButtonElement(document.getElementById('btn_task_picker_save'));
if Assigned(btn) then
btn.disabled := not AEnabled;
end;
procedure TTaskPickerOffCanvas.Show;
begin
asm
const el = document.getElementById('task_picker_offcanvas');
if (!el || !window.bootstrap) return;
const oc = bootstrap.Offcanvas.getOrCreateInstance(el);
oc.show();
end;
end;
procedure TTaskPickerOffCanvas.Hide;
begin
asm
const el = document.getElementById('task_picker_offcanvas');
if (!el || !window.bootstrap) return;
const oc = bootstrap.Offcanvas.getOrCreateInstance(el);
oc.hide();
end;
end;
[async] procedure TTaskPickerOffCanvas.LoadCustomers;
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
response := await(FClient.RawInvokeAsync(
'ITimeEntryService.GetTaskPickerCustomers',
[FUserId]
));
resultObj := TJSObject(response.Result);
FCustomers := TJSArray(resultObj['options']);
FillSelect('task_picker_customer', FCustomers);
end;
[async] procedure TTaskPickerOffCanvas.LoadProjects(const ACustomerId: string);
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
response := await(FClient.RawInvokeAsync(
'ITimeEntryService.GetTaskPickerProjects',
[FUserId, ACustomerId]
));
resultObj := TJSObject(response.Result);
FProjects := TJSArray(resultObj['options']);
FillSelect('task_picker_project', FProjects);
end;
[async] procedure TTaskPickerOffCanvas.LoadTasks(const AProjectId: string);
var
response: TXDataClientResponse;
resultObj: TJSObject;
begin
response := await(FClient.RawInvokeAsync(
'ITimeEntryService.GetTaskPickerTasks',
[FUserId, AProjectId]
));
resultObj := TJSObject(response.Result);
FTasks := TJSArray(resultObj['options']);
FillSelect('task_picker_task', FTasks);
end;
[async] procedure TTaskPickerOffCanvas.CustomerChanged(Event: TJSEvent);
var
customerId: string;
begin
ClearStatus;
SetSaveEnabled(False);
ClearSelect('task_picker_project', True);
ClearSelect('task_picker_task', True);
customerId := SelectedValue('task_picker_customer');
if customerId = '' then
Exit;
try
await(LoadProjects(customerId));
except
on E: Exception do
SetStatus('Unable to load projects.');
end;
end;
[async] procedure TTaskPickerOffCanvas.ProjectChanged(Event: TJSEvent);
var
projectId: string;
begin
ClearStatus;
SetSaveEnabled(False);
ClearSelect('task_picker_task', True);
projectId := SelectedValue('task_picker_project');
if projectId = '' then
Exit;
try
await(LoadTasks(projectId));
except
on E: Exception do
SetStatus('Unable to load tasks.');
end;
end;
procedure TTaskPickerOffCanvas.TaskChanged(Event: TJSEvent);
begin
ClearStatus;
SetSaveEnabled(SelectedValue('task_picker_task') <> '');
end;
procedure TTaskPickerOffCanvas.SaveClicked(Event: TJSEvent);
var
taskId: string;
taskDisplay: string;
begin
Event.preventDefault;
taskId := SelectedValue('task_picker_task');
if taskId = '' then
begin
SetStatus('Select a task.');
Exit;
end;
taskDisplay := FindDisplay(FTasks, taskId);
if Assigned(FOnTaskPicked) then
FOnTaskPicked(FRowIndex, taskId, taskDisplay);
Hide;
end;
[async] procedure TTaskPickerOffCanvas.Open(const AUserId: string; ARowIndex: Integer);
begin
FUserId := AUserId;
FRowIndex := ARowIndex;
ClearStatus;
SetSaveEnabled(False);
ClearSelect('task_picker_customer', True);
ClearSelect('task_picker_project', True);
ClearSelect('task_picker_task', True);
Show;
try
await(LoadCustomers);
except
on E: Exception do
SetStatus('Unable to load customers.');
end;
end;
end.
...@@ -6,6 +6,9 @@ object ApiDatabase: TApiDatabase ...@@ -6,6 +6,9 @@ object ApiDatabase: TApiDatabase
AutoCommit = False AutoCommit = False
ProviderName = 'MySQL' ProviderName = 'MySQL'
Database = 'eTask' Database = 'eTask'
Username = 'root'
Server = '192.168.102.131'
Connected = True
LoginPrompt = False LoginPrompt = False
Left = 435 Left = 435
Top = 359 Top = 359
...@@ -1149,8 +1152,14 @@ object ApiDatabase: TApiDatabase ...@@ -1149,8 +1152,14 @@ object ApiDatabase: TApiDatabase
'SET' 'SET'
' TASK_DATE = :TASK_DATE,' ' TASK_DATE = :TASK_DATE,'
' TASK_ID = :TASK_ID,' ' TASK_ID = :TASK_ID,'
' PROJECT_ID = ('
' SELECT t.PROJECT_ID'
' FROM tasks t'
' WHERE t.TASK_ID = :TASK_ID'
' ),'
' HOURS = :HOURS,' ' HOURS = :HOURS,'
' TASK_TIME = :TASK_TIME,' ' TASK_TIME = :TASK_TIME,'
' PLACE = :PLACE,'
' CATEGORY = :CATEGORY,' ' CATEGORY = :CATEGORY,'
' SUMMARY = :SUMMARY,' ' SUMMARY = :SUMMARY,'
' MODIFY_DATE = now(),' ' MODIFY_DATE = now(),'
...@@ -1182,6 +1191,11 @@ object ApiDatabase: TApiDatabase ...@@ -1182,6 +1191,11 @@ object ApiDatabase: TApiDatabase
end end
item item
DataType = ftUnknown DataType = ftUnknown
Name = 'PLACE'
Value = nil
end
item
DataType = ftUnknown
Name = 'CATEGORY' Name = 'CATEGORY'
Value = nil Value = nil
end end
...@@ -1206,4 +1220,145 @@ object ApiDatabase: TApiDatabase ...@@ -1206,4 +1220,145 @@ object ApiDatabase: TApiDatabase
Value = nil Value = nil
end> end>
end end
object uqTaskPickerCustomers: TUniQuery
Connection = ucETaskApi
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 = 402
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 = 10
end
end
object uqTaskPickerProjects: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select distinct'
' 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')
Left = 544
Top = 494
ParamData = <
item
DataType = ftUnknown
Name = 'USER_ID'
Value = nil
end
item
DataType = ftUnknown
Name = 'CUSTOMER_ID'
Value = nil
end>
object uqTaskPickerProjectsPROJECT_ID: TStringField
FieldName = 'PROJECT_ID'
Required = True
Size = 7
end
object uqTaskPickerProjectsPROJECT_NAME: TStringField
FieldName = 'PROJECT_NAME'
Size = 30
end
end
object uqTaskPickerTasks: TUniQuery
Connection = ucETaskApi
SQL.Strings = (
'select distinct'
' t.TASK_ID,'
' c.SHORT_NAME as CUSTOMER_SHORT_NAME,'
' p.NAME as PROJECT_NAME,'
' t.TASK_NUM_1,'
' t.TASK_NUM_2,'
' t.TASK_NUM_3,'
' t.TASK_NUM_4,'
' t.TASK_NUM_5,'
' t.TASK_NUM_6,'
' t.SUBJECT as TASK_SUBJECT'
'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'
'order by'
' t.TASK_NUM_1,'
' t.TASK_NUM_2,'
' t.TASK_NUM_3,'
' t.TASK_NUM_4,'
' t.TASK_NUM_5,'
' t.TASK_NUM_6,'
' 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>
object uqTaskPickerTasksTASK_ID: TStringField
FieldName = 'TASK_ID'
Required = True
Size = 7
end
object uqTaskPickerTasksCUSTOMER_SHORT_NAME: TStringField
FieldName = 'CUSTOMER_SHORT_NAME'
ReadOnly = True
Size = 10
end
object uqTaskPickerTasksPROJECT_NAME: TStringField
FieldName = 'PROJECT_NAME'
ReadOnly = True
Size = 30
end
object uqTaskPickerTasksTASK_NUM_1: TIntegerField
FieldName = 'TASK_NUM_1'
end
object uqTaskPickerTasksTASK_NUM_2: TIntegerField
FieldName = 'TASK_NUM_2'
end
object uqTaskPickerTasksTASK_NUM_3: TIntegerField
FieldName = 'TASK_NUM_3'
end
object uqTaskPickerTasksTASK_NUM_4: TIntegerField
FieldName = 'TASK_NUM_4'
end
object uqTaskPickerTasksTASK_NUM_5: TIntegerField
FieldName = 'TASK_NUM_5'
end
object uqTaskPickerTasksTASK_NUM_6: TIntegerField
FieldName = 'TASK_NUM_6'
end
object uqTaskPickerTasksTASK_SUBJECT: TStringField
FieldName = 'TASK_SUBJECT'
Size = 80
end
end
end end
...@@ -90,6 +90,23 @@ type ...@@ -90,6 +90,23 @@ type
uqProjectReportedUsersNAME: TStringField; uqProjectReportedUsersNAME: TStringField;
uqAddTimeEntry: TUniQuery; uqAddTimeEntry: TUniQuery;
uqSaveTimeEntry: TUniQuery; uqSaveTimeEntry: TUniQuery;
uqTaskPickerCustomers: TUniQuery;
uqTaskPickerProjects: TUniQuery;
uqTaskPickerTasks: TUniQuery;
uqTaskPickerCustomersCUSTOMER_ID: TStringField;
uqTaskPickerCustomersCUSTOMER_SHORT_NAME: TStringField;
uqTaskPickerProjectsPROJECT_ID: TStringField;
uqTaskPickerProjectsPROJECT_NAME: TStringField;
uqTaskPickerTasksTASK_ID: TStringField;
uqTaskPickerTasksCUSTOMER_SHORT_NAME: TStringField;
uqTaskPickerTasksPROJECT_NAME: TStringField;
uqTaskPickerTasksTASK_NUM_1: TIntegerField;
uqTaskPickerTasksTASK_NUM_2: TIntegerField;
uqTaskPickerTasksTASK_NUM_3: TIntegerField;
uqTaskPickerTasksTASK_NUM_4: TIntegerField;
uqTaskPickerTasksTASK_NUM_5: TIntegerField;
uqTaskPickerTasksTASK_NUM_6: TIntegerField;
uqTaskPickerTasksTASK_SUBJECT: TStringField;
procedure DataModuleCreate(Sender: TObject); procedure DataModuleCreate(Sender: TObject);
procedure uqUsersCalcFields(DataSet: TDataSet); procedure uqUsersCalcFields(DataSet: TDataSet);
private private
......
...@@ -20,6 +20,8 @@ type ...@@ -20,6 +20,8 @@ type
taskDisplay: string; taskDisplay: string;
hours: Nullable<Double>; hours: Nullable<Double>;
taskTime: string; taskTime: string;
place: string;
placeDesc: string;
category: string; category: string;
categoryDesc: string; categoryDesc: string;
summary: string; summary: string;
...@@ -44,6 +46,7 @@ type ...@@ -44,6 +46,7 @@ type
items: TList<TTimeEntry>; items: TList<TTimeEntry>;
taskOptions: TList<TTimeEntryTaskOption>; taskOptions: TList<TTimeEntryTaskOption>;
categoryOptions: TList<TTimeEntryCategoryOption>; categoryOptions: TList<TTimeEntryCategoryOption>;
placeOptions: TList<TTimeEntryCategoryOption>;
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
end; end;
...@@ -56,16 +59,45 @@ type ...@@ -56,16 +59,45 @@ type
taskId: string; taskId: string;
hours: Double; hours: Double;
taskTime: string; taskTime: string;
place: string;
category: string; category: string;
summary: string; summary: string;
end; end;
TTimeEntryFieldSave = class
public
entryId: Integer;
userId: string;
fieldName: string;
value: string;
end;
type
TTaskPickerOption = class
public
value: string;
display: string;
end;
TTaskPickerOptionsResponse = class
public
options: TList<TTaskPickerOption>;
constructor Create;
destructor Destroy; override;
end;
[ServiceContract, Model(API_MODEL)] [ServiceContract, Model(API_MODEL)]
ITimeEntryService = interface(IInvokable) ITimeEntryService = interface(IInvokable)
['{B18BCD1E-B19A-4D25-BBA9-50A24FC4C690}'] ['{B18BCD1E-B19A-4D25-BBA9-50A24FC4C690}']
[HttpGet] function GetTimeEntries(userId, startDate, endDate: string): TTimeEntriesResponse; [HttpGet] function GetTimeEntries(userId, startDate, endDate: string): TTimeEntriesResponse;
[HttpPost] function AddTimeEntry(userId, taskDate: string): string; [HttpPost] function AddTimeEntry(userId, taskDate: string): string;
[HttpPost] function SaveTimeEntry(Item: TTimeEntrySave): Boolean; [HttpPost] function SaveTimeEntry(Item: TTimeEntrySave): Boolean;
[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;
end; end;
implementation implementation
...@@ -76,6 +108,7 @@ begin ...@@ -76,6 +108,7 @@ begin
items := TList<TTimeEntry>.Create; items := TList<TTimeEntry>.Create;
taskOptions := TList<TTimeEntryTaskOption>.Create; taskOptions := TList<TTimeEntryTaskOption>.Create;
categoryOptions := TList<TTimeEntryCategoryOption>.Create; categoryOptions := TList<TTimeEntryCategoryOption>.Create;
placeOptions := TList<TTimeEntryCategoryOption>.Create;
end; end;
destructor TTimeEntriesResponse.Destroy; destructor TTimeEntriesResponse.Destroy;
...@@ -83,6 +116,19 @@ begin ...@@ -83,6 +116,19 @@ begin
items.Free; items.Free;
taskOptions.Free; taskOptions.Free;
categoryOptions.Free; categoryOptions.Free;
placeOptions.Free;
inherited;
end;
constructor TTaskPickerOptionsResponse.Create;
begin
inherited;
options := TList<TTaskPickerOption>.Create;
end;
destructor TTaskPickerOptionsResponse.Destroy;
begin
options.Free;
inherited; inherited;
end; 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=222 LogFileNum=236
[Database] [Database]
Server=192.168.102.131 Server=192.168.102.131
......
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