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.
