// Displays items in the grid. Allows the user
// to sort the entries, filter their search, and search for a specific person.
// Authors:
// Cameron Hayes
// Mac Stephens

unit View.Items;

interface

uses
  System.SysUtils, System.Classes, Web, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
  Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids, WebLib.Lists,
  XData.Web.Client, WEBLib.ExtCtrls, DB, XData.Web.JsonDataset, JS, WEBLib.JSON,
  XData.Web.Dataset, XData.Web.Connection, Vcl.Forms, DateUtils, WEBLib.DBCtrls;

type
  TFViewItems = class(TWebForm)
    XDataWebClient1: TXDataWebClient;
    xdwdsItems: TXDataWebDataSet;
    lblEntries: TWebLabel;
    wcbPageSize: TWebComboBox;
    btnAdd: TWebButton;
    btnSave: TWebButton;
    btnCancel: TWebButton;
    btnDelete: TWebButton;
    btnUpdate: TWebButton;
    wdbtcItems: TWebDBTableControl;
    wdsItems: TWebDataSource;
    xdwdsItemsstatus: TStringField;
    xdwdsItemsID: TStringField;
    xdwdsItemsname: TStringField;
    xdwdsItemsdescription: TStringField;
    edtName: TWebDBEdit;
    edtDescription: TWebDBEdit;
    CBStatus: TWebDBCheckBox;
    xdwdsItemsQB_ID: TStringField;
    lblFormState: TWebLabel;
    procedure btnAddClick(Sender: TObject);
    procedure wcbPageSizeChange(Sender: TObject);
    procedure btnUpdateClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnDeleteClick(Sender: TObject);
    procedure btnCancelClick(Sender: TObject);
    procedure wdbtcItemsClickCell(Sender: TObject; ACol, ARow: Integer);
  private
    FChildForm: TWebForm;
    procedure AddRowToTable(ID, Name, Description, Status: string);
    procedure ClearTable();
    procedure GeneratePagination(TotalPages: Integer);
    function GenerateSearchOptions(): string;
    procedure ViewMode();
    procedure EditMode();
    procedure DisablePagination();
    procedure EnablePagination();
    [async] procedure UpdateItem();
    [async] procedure GetItems(searchOptions: string);
    [async] procedure AddItem();
    [async] procedure InitializeForm;
    var
      PageNumber: integer;
      PageSize: integer;
      TotalPages: integer;
      StartDate: string;
      EndDate: string;
      OrderBy: string;
      Caller: string;
      notification: string;
      mode: string;
   public
    class function CreateForm(AElementID: string): TWebForm;

  end;

var
  FViewItems: TFViewItems;

implementation

uses
  XData.Model.Classes,
  ConnectionModule, Auth.Service, Utils, View.Item.Add, View.Main;

{$R *.dfm}

class function TFViewItems.CreateForm(AElementID: string): TWebForm;
begin
  Application.CreateForm(TFViewItems, AElementID, Result,
    procedure(AForm: TObject)
    begin
      with TFViewItems(AForm) do
      begin
        InitializeForm;
      end;
    end
  );
end;

[async] procedure TFViewItems.InitializeForm;
// Initializes important values:
// PageNumber: What page number the user is on IE 1: 1-10, 2: 11-20 etc
// TotalPages: Total number of pages returned from the search.
// PageSize: Number of entries per page.
begin
  DMConnection.ApiConnection.Connected := True;
  PageNumber := 1;
  TotalPages := 1; // Initial total pages
  wcbPageSize.Text := '10';
  await(getItems(GenerateSearchOptions()));
  ViewMode();
end;

procedure TFViewItems.AddRowToTable(ID, Name, Description, Status: string);
// Adds one row to #tblPhoneGrid and lets Bootstrap 5.3 highlight the row
// with its built-in `table-active` class when the user clicks it.
var
  NewRow, Cell: TJSHTMLElement;
begin
  NewRow := TJSHTMLElement(document.createElement('tr'));

  // Row-select click handler
  NewRow.addEventListener('click',
    procedure(Event: TJSMouseEvent)
    var
      TBody    : TJSHTMLElement;
      Rows     : TJSHTMLCollection;
      I        : Integer;
      RowElem  : TJSHTMLElement;
    begin
      // Grab the <tbody> once and cast it
      TBody := TJSHTMLElement(
                 (document.getElementById('tblPhoneGrid') as TJSHTMLElement)
                   .getElementsByTagName('tbody')[0]
               );

      // Remove 'table-active' from every existing row
      Rows := TBody.children;
      for I := 0 to Rows.length - 1 do
      begin
        RowElem := TJSHTMLElement(Rows.item(I));   // cast Node HTMLElement
        RowElem.classList.remove('table-primary');
      end;

      // Add highlight to the clicked row
      TJSHTMLElement(Event.currentTarget).classList.add('table-primary');
    end
  );

  Cell := TJSHTMLElement(document.createElement('td'));
  Cell.setAttribute('data-label', 'Item ID');
  Cell.innerText := ID;
  NewRow.appendChild(Cell);

  Cell := TJSHTMLElement(document.createElement('td'));
  Cell.setAttribute('data-label', 'Name');
  Cell.innerText := Name;
  NewRow.appendChild(Cell);

  Cell := TJSHTMLElement(document.createElement('td'));
  Cell.setAttribute('data-label', 'Description');
  Cell.innerText := Description;
  NewRow.appendChild(Cell);

  Cell := TJSHTMLElement(document.createElement('td'));
  Cell.setAttribute('data-label', 'Status');
  Cell.innerText := Status;
  NewRow.appendChild(Cell);

  TJSHTMLElement(
    (document.getElementById('tblPhoneGrid') as TJSHTMLElement)
      .getElementsByTagName('tbody')[0]
  ).appendChild(NewRow);

  Utils.HideSpinner('spinner');
end;

procedure TFViewItems.DisablePagination;
var
  i: Integer;
  li: TJSHTMLElement;
  ul: TJSHTMLElement;
begin
  ul := TJSHTMLElement(document.getElementById('pagination'));
  if Assigned(ul) then
  begin
    for i := 0 to ul.children.length - 1 do
    begin
      li := TJSHTMLElement(ul.children[i]);
      li.setAttribute('disabled', 'true');
      li.style.setProperty('pointer-events', 'none');       // Disable click
      li.style.setProperty('opacity', '0.5');
    end;
  end;
end;

procedure TFViewItems.EnablePagination;
var
  i: Integer;
  li: TJSHTMLElement;
  ul: TJSHTMLElement;
begin
  ul := TJSHTMLElement(document.getElementById('pagination'));
  if Assigned(ul) then
  begin
    for i := 0 to ul.children.length - 1 do
    begin
      li := TJSHTMLElement(ul.children[i]);
      li.removeAttribute('disabled');
      li.style.removeProperty('pointer-events');
      li.style.removeProperty('opacity');
    end;
  end;
end;

procedure TFViewItems.GeneratePagination(TotalPages: Integer);
// Generates pagination for the table.
// TotalPages: Total amount of pages generated by the search
var
  PaginationElement, PageItem, PageLink: TJSHTMLElement;
  I, Start, Finish: Integer;
begin
  PaginationElement := TJSHTMLElement(document.getElementById('pagination'));
  PaginationElement.innerHTML := ''; // Clear existing pagination

  // Previous Button
  PageItem := TJSHTMLElement(document.createElement('li'));
  PageItem.className := 'page-item';
  if PageNumber = 1 then
    PageItem.classList.add('disabled');
  PageLink := TJSHTMLElement(document.createElement('a'));
  PageLink.className := 'page-link';
  PageLink.innerText := 'Previous';
  PageLink.setAttribute('href', 'javascript:void(0)');
  PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
  begin
    if ( ( PageNumber > 1 ) and ( not FViewMain.change ) ) then
    begin
      Dec(PageNumber);
      GetItems(GenerateSearchOptions());
    end;
  end);
  PageItem.appendChild(PageLink);
  PaginationElement.appendChild(PageItem);

  // Page Numbers

  if PageNumber <= 4 then
  // If page number is low enough no early elipsis needed
  Begin
    Start := 2;
    Finish := 5;
  End
  else if (PageNumber >= (TotalPages - 3)) then
  // If page number is high enough no late elipsis needed
  begin
    Start := TotalPages - 3;
    Finish := TotalPages - 1;
  end
  else
  begin
    Start := PageNumber - 1;
    Finish := PageNumber + 1;
  end;

  PageItem := TJSHTMLElement(document.createElement('li'));
  PageItem.className := 'page-item';
  if 1 = PageNumber then
    PageItem.classList.add('selected-number'); // Add the selected-number class
  PageLink := TJSHTMLElement(document.createElement('a'));
  PageLink.className := 'page-link';
  PageLink.innerText := '1';
  PageLink.setAttribute('href', 'javascript:void(0)');
  PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
  var
    PageNum: Integer;
  begin
    if ( not FViewMain.change ) then
    begin
      PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
      PageNumber := PageNum;
      GetItems(GenerateSearchOptions());
    end;
  end);
  PageItem.appendChild(PageLink);
  PaginationElement.appendChild(PageItem);

  // Adds Elipse to pagination if page number is too big
  if PageNumber > 4 then
  begin
    PageItem := TJSHTMLElement(document.createElement('li'));
    PageItem.className := 'page-item';
    PageItem.classList.add('disabled');
    PageLink := TJSHTMLElement(document.createElement('a'));
    PageLink.className := 'page-link';
    PageLink.innerText := '...';
    PageLink.setAttribute('href', 'javascript:void(0)');
    PageItem.appendChild(PageLink);
    PaginationElement.appendChild(PageItem);
  end;


  // Adds Page, page - 1, and page + 1 to pagination
  for I := Start  to Finish do
  begin
    if ( I > 1) and (I < TotalPages) then
    begin
      PageItem := TJSHTMLElement(document.createElement('li'));
      PageItem.className := 'page-item';
      if I = PageNumber then
        PageItem.classList.add('selected-number'); // Add the selected-number class
      PageLink := TJSHTMLElement(document.createElement('a'));
      PageLink.className := 'page-link';
      PageLink.innerText := IntToStr(I);
      PageLink.setAttribute('href', 'javascript:void(0)');
      PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
      var
        PageNum: Integer;
      begin
        if ( not FViewMain.change ) then
        begin
          PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
          PageNumber := PageNum;
          GetItems(GenerateSearchOptions());
        end;
      end);
      PageItem.appendChild(PageLink);
      PaginationElement.appendChild(PageItem);
    end;
  end;

  // adds ellipse if number is too small
  if PageNumber < TotalPages - 4 then
  begin
    PageItem := TJSHTMLElement(document.createElement('li'));
    PageItem.className := 'page-item';
    PageItem.classList.add('disabled');
    PageLink := TJSHTMLElement(document.createElement('a'));
    PageLink.className := 'page-link';
    PageLink.innerText := '...';
    PageLink.setAttribute('href', 'javascript:void(0)');
    PageItem.appendChild(PageLink);
    PaginationElement.appendChild(PageItem);
  end;

  if TotalPages <> 1 then
  begin
    PageItem := TJSHTMLElement(document.createElement('li'));
    PageItem.className := 'page-item';
    if TotalPages = PageNumber then
          PageItem.classList.add('selected-number');
    PageLink := TJSHTMLElement(document.createElement('a'));
    PageLink.className := 'page-link';
    PageLink.innerText := IntToStr(TotalPages);
    PageLink.setAttribute('href', 'javascript:void(0)');
    PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
    var
      PageNum: Integer;
    begin
      if ( not FViewMain.change ) then
      begin
        PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
        PageNumber := PageNum;
        GetItems(generateSearchOptions());
      end;
    end);
  end;
  PageItem.appendChild(PageLink);
  PaginationElement.appendChild(PageItem);

  // Next Button
  PageItem := TJSHTMLElement(document.createElement('li'));
  PageItem.className := 'page-item';
  if PageNumber = TotalPages then
    PageItem.classList.add('disabled');
  PageLink := TJSHTMLElement(document.createElement('a'));
  PageLink.className := 'page-link';
  PageLink.innerText := 'Next';
  PageLink.setAttribute('href', 'javascript:void(0)');
  PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
  begin
    if ( ( PageNumber < TotalPages ) and ( not FViewMain.change ) ) then
    begin
      Inc(PageNumber);
      GetItems(GenerateSearchOptions());
    end;
  end);
  PageItem.appendChild(PageLink);
  PaginationElement.appendChild(PageItem);

end;

procedure TFViewItems.GetItems(searchOptions: string);
// retrieves items from the server.
// searchOptions: info to limit how many items we retrieve from the server.
// probably not needed due to database size.
var
  xdcResponse: TXDataClientResponse;
  itemList : TJSObject;
  i: integer;
  data: TJSArray;
  item: TJSObject;
  itemListLength: integer;
begin
  if PageNumber > 0 then
  begin
    Utils.ShowSpinner('spinner');
    try
      xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.GetItems',
          [searchOptions]));
      itemList :=  TJSObject(xdcResponse.Result);
      data := TJSArray(itemList['data']);
      itemListLength := integer(itemList['count']);
      xdwdsItems.Close;
      xdwdsItems.SetJsonData(data);
      xdwdsItems.Open;


      TotalPages := (itemListLength + PageSize - 1) div PageSize;
      if (PageNumber * PageSize) < itemListLength then
      begin
        lblEntries.Caption := 'Showing entries ' + IntToStr((PageNumber - 1) * PageSize + 1) +
                              ' - ' + IntToStr(PageNumber * PageSize) +
                               ' of ' + IntToStr(itemListLength);
      end
      else
      begin
         lblEntries.Caption := 'Showing entries ' + IntToStr((PageNumber - 1) * PageSize + 1) +
                              ' - ' + IntToStr(itemListLength) +
                               ' of ' + IntToStr(itemListLength);
      end;
      GeneratePagination(TotalPages);
    except
      on E: EXDataClientRequestException do
        Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
    end;
    Utils.HideSpinner('spinner');
  end;
end;

procedure TFViewItems.btnAddClick(Sender: TObject);
var
  itemOptions, AccessType: string;
  newform: TFViewAddItem;
begin
  AccessType := JS.toString(AuthService.TokenPayload.Properties['user_access_type']);
  if( ( AccessType =  'ADMIN' ) or ( AccessType = 'QBUSR' ) ) then
  begin
    newform := TFViewAddItem.CreateNew;

    newform.Caption := 'Select Item to Add';
    newForm.Popup := True;
    newForm.position:= poScreenCenter;
    newForm.Border := fbDialog;

    // used to manage Back button handling to close subform
    window.location.hash := 'subform';

    newform.ShowModal(
      procedure(AValue: TModalResult)
      begin
        if newform.confirm then
        begin
          xdwdsItems.Append;

          xdwdsItems.FieldByName('QB_ID').AsString := newform.QB_ID;
          xdwdsItems.FieldByName('name').AsString := newform.name;
          xdwdsItems.FieldByName('description').AsString := newform.description;
          xdwdsItems.FieldByName('status').AsString := newform.status;
          console.log(xdwdsItems.FieldByName('status').AsString);
          console.log(newform.status);

          xdwdsItems.Post;
          EditMode();
          lblFormState.Caption := 'Add Mode';
        end;
      end
    );
  end
  else
    ShowToast('Failure:User not authorized to add item from QuickBooks', 'failure');

end;

procedure TFViewItems.btnCancelClick(Sender: TObject);
begin
  ShowConfirmationModal(
    'Are you sure you want to cancel?',
    'Yes',
    'No',
    procedure(confirmed: Boolean)
    begin
      if confirmed then
      begin
        FViewMain.change := false;
        ViewMode();
        ShowToast('failure:Changes Discarded');
        TJSHTMLInputElement(document.getElementById('edtname')).classList.remove('changed-field');
        TJSHTMLInputElement(document.getElementById('edtdescription')).classList.remove('changed-field');
        TJSHTMLInputElement(document.getElementById('lblactive')).classList.remove('changed-field-label');
        xdwdsItems.Cancel;
        xdwdsItems.First;
        getItems(GenerateSearchOptions());
      end;
    end);
end;


procedure TFViewItems.btnDeleteClick(Sender: TObject);
begin
  ShowToast('Deleting items is not yet implemented.', 'info');
end;

procedure TFViewItems.btnUpdateClick(Sender: TObject);
var
  itemOptions, AccessType: string;
  newform: TFViewAddItem;
begin
  AccessType := JS.toString(AuthService.TokenPayload.Properties['user_access_type']);
  if( ( AccessType =  'ADMIN' ) or ( AccessType = 'QBUSR' ) ) then
  begin
    UpdateItem();
  end
  else
    ShowToast('Failure:User not authorized to update item from QuickBooks', 'failure');

end;

procedure TFViewItems.UpdateItem();
var
  item: TJSObject;
  xdcResponse: TXDataClientResponse;
  msg, temp: string;
  change: boolean;
  input: TJSHTMLInputElement;
begin
  try
    Utils.ShowSpinner('spinner');
    xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.UpdateItem', [xdwdsItemsname.AsString]));
    item :=  TJSObject(xdcResponse.Result);
    change := false;
    xdwdsItems.Edit;

    input := TJSHTMLInputElement(document.getElementById('edtname'));
    if string(item['name']) <> xdwdsItemsname.AsString then
    begin
      input.classList.add('changed-field');
      change := true;
      xdwdsItemsname.AsString := string(item['name']);
    end
    else
      input.classList.remove('changed-field');

    input := TJSHTMLInputElement(document.getElementById('edtdescription'));
    if string(item['description']) <> xdwdsItemsdescription.AsString then
    begin
      input.classList.add('changed-field');
      change := true;
      xdwdsItemsdescription.AsString := string(item['description']);
    end
    else
      input.classList.remove('changed-field');

    input := TJSHTMLInputElement(document.getElementById('lblactive'));
    if string(item['status']) <> xdwdsItemsstatus.AsString then
    begin
      input.classList.add('changed-field-label');
      change := true;
      xdwdsItemsstatus.AsString := string(item['status']);
    end
    else
      input.classList.remove('changed-field-label');

    if string(item['QB_ID']) <> xdwdsItemsQB_ID.AsString then
    begin
      change := true;
      xdwdsItemsQB_ID.AsString := string(item['QB_ID']);
    end;

    xdwdsItems.Post;
    Utils.HideSpinner('spinner');

    if change then
    begin
      EditMode;
      ShowToast('Item updated based on QuickBooks data');
    end
    else
      ShowToast('No Changes needed');
  except
    on E: EXDataClientRequestException do
      Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
  end;
end;

procedure TFViewItems.btnSaveClick(Sender: TObject);
//TODO implement editting items
begin
  //ShowToast('Editing items is not yet implemented.', 'info');
  if lblFormState.Caption = 'Edit Mode' then
    mode := 'EDIT'
  else
    mode := 'ADD';
  xdwdsItems.Post;
  AddItem();
  ViewMode();
end;


procedure TFViewItems.AddItem();
// adds an item to the database.
var
  xdcResponse: TXDataClientResponse;
  item: TJSObject;
  itemJSON: TJSONObject;
begin
  try
    itemJSON := TJSONObject.Create;
    itemJSON.AddPair('qb_items_id', xdwdsItems.FieldByName('ID').AsString);
    itemJSON.AddPair('qb_item_name', xdwdsItems.FieldByName('name').AsString);
    itemJSON.AddPair('item_desc', xdwdsItems.FieldByName('description').AsString);
    itemJSON.AddPair('status', xdwdsItems.FieldByName('status').AsString);
    itemJSON.AddPair('qb_items_qb_id', xdwdsItems.FieldByName('QB_ID').AsString);
    itemJSON.AddPair('mode', mode);

    xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.AddItem', [itemJSON.ToString]));
    getItems(GenerateSearchOptions());
    item := TJSObject(xdcResponse.Result);
    showToast(string(item['msg']));
    TJSHTMLInputElement(document.getElementById('edtname')).classList.remove('changed-field');
    TJSHTMLInputElement(document.getElementById('edtdescription')).classList.remove('changed-field');
    TJSHTMLInputElement(document.getElementById('lblactive')).classList.remove('changed-field-label');

  except
    on E: EXDataClientRequestException do
      Utils.ShowErrorModal(E.ErrorResult.ErrorMessage);
  end;
end;


procedure TFViewItems.wcbPageSizeChange(Sender: TObject);
// gets a new amount of items based when the page size is changed
begin
  PageNumber := 1;
  getItems(GenerateSearchOptions());
end;

procedure TFViewItems.wdbtcItemsClickCell(Sender: TObject; ACol, ARow: Integer);
begin
  if lblFormState.Caption = 'View Mode' then
  begin
    xdwdsItems.Locate('ID', wdbtcItems.Cells[0, ARow], []);
  end;
end;

procedure TFViewItems.ClearTable();
// clears the table
var
  tbody: TJSHTMLElement;
begin
  tbody := TJSHTMLElement(document.getElementById('tblPhoneGrid').getElementsByTagName('tbody')[0]);
  tbody.innerHTML := '';
end;

function TFViewItems.GenerateSearchOptions(): string;
// Generates searchOptions for GetItems.
var
searchOptions: string;
begin
  PageSize := StrToInt(wcbPageSize.Text);
  searchOptions :=  '&pagenumber=' + IntToStr(PageNumber) +
                    '&pagesize=' + IntToStr(PageSize) +
                    '&orderby=' + OrderBy;
  Result := searchOptions;
end;

procedure TFViewItems.ViewMode;
// Enables Customer Fields while disabling shipping address fields.
begin
  wcbPageSize.Enabled := true;
  wdbtcItems.Enabled := true;

  EnablePagination();
  btnAdd.Enabled := true;
  btnDelete.Enabled := true;
  btnSave.Enabled := false;
  btnCancel.Enabled := false;
  btnUpdate.Enabled := true;
  FViewMain.change := false;

  lblFormState.Caption := 'View Mode';
  lblFormState.ElementHandle.classList.remove('text-success');
  lblFormState.ElementHandle.classList.add('text-danger');

end;

procedure TFViewItems.EditMode();
begin
  xdwdsItems.Edit;
  wdbtcItems.Enabled := false;

  wcbPageSize.Enabled := false;

  FViewMain.change := true;
  btnAdd.Enabled := false;
  btnDelete.Enabled := false;
  btnSave.Enabled := true;
  btnCancel.Enabled := True;
  btnUpdate.Enabled := false;

  DisablePagination();

  lblFormState.Caption := 'Edit Mode';
  lblFormState.ElementHandle.classList.remove('text-danger');
  lblFormState.ElementHandle.classList.add('text-success');
end;


end.

