unit frm_BaseList;
interface
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.Grids,
  Vcl.DBGrids, Data.DB, Vcl.Buttons, uAppTheme, FireDAC.Comp.Client, uDBGridHelper, uUIAutoScaler,
  Vcl.ComCtrls, FireDAC.Stan.Option, System.Generics.Collections, System.UITypes;
type
  TfrmBaseList = class(TForm)
    pnlMain: TPanel;
    pnlActions: TPanel;
    pnlGrid: TPanel;
    dbGridMain: TDBGrid;
    dsMain: TDataSource;
    btnBorrar: TSpeedButton;
    btnEditar: TSpeedButton;
    btnNuevo: TSpeedButton;
    pnlSearch: TPanel;
    lblSearch: TLabel;
    edtSearch: TEdit;
    btnSearch: TSpeedButton;
    btnHelp: TSpeedButton;
    btnPrint: TSpeedButton;
    lblRecordCount: TLabel;
    pnlLeftFilter: TPanel;
    grpStatuses: TGroupBox;
    grpDates: TGroupBox;
    rbDateAll: TRadioButton;
    rbDateToday: TRadioButton;
    rbDateWeek: TRadioButton;
    rbDateMonth: TRadioButton;
    rbDateCustom: TRadioButton;
    lblDateStart: TLabel;
    dtpStart: TDateTimePicker;
    lblDateEnd: TLabel;
    dtpEnd: TDateTimePicker;
    pnlAlphaFilter: TPanel;
    procedure FormShow(Sender: TObject);
    procedure btnRefreshClick(Sender: TObject); virtual;
    procedure btnNewClick(Sender: TObject); virtual;
    procedure btnEditClick(Sender: TObject); virtual;
    procedure btnDeleteClick(Sender: TObject); virtual;
    procedure btnPrintClick(Sender: TObject); virtual;
    procedure btnHelpClick(Sender: TObject);
    procedure btnSearchClick(Sender: TObject);
    procedure edtSearchKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure edtSearchChange(Sender: TObject);
    procedure dbGridMainDblClick(Sender: TObject);
    procedure dbGridMainTitleClick(Column: TColumn);
    procedure DateFilterClick(Sender: TObject);
    procedure AlphaFilterClick(Sender: TObject);
    procedure StatusFilterClick(Sender: TObject);
    procedure dtpChange(Sender: TObject);
  private
    FLastSortColumn: string;
    FLastSortDirection: string;
    FSelectedLetter: string;
    FAlphaFilterField: string;
    FStatusFilterField: string;
    FDateFilterField: string;
    FStatusValues: TStringList;
    FTmrSearch: TTimer;
    FSearchDelay: Integer;
    procedure CreateAlphaFilterButtons;
    procedure CreateStatusCheckboxes;
    procedure QryMainAfterOpen(DataSet: TDataSet);
    function IsBoolFilterField(const AFieldName: string): Boolean;
    procedure OnSearchTimer(Sender: TObject);
  protected
    FQryMain: TFDQuery;
    procedure DoSearch; virtual;
    procedure ApplyAllFilters; virtual;
    procedure dbGridMainDrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetSelectedOrCurrentIds(const AIdFieldName: string = 'id'): TArray<string>;
    property QryMain: TFDQuery read FQryMain;
    property AlphaFilterField: string read FAlphaFilterField write FAlphaFilterField;
    property StatusFilterField: string read FStatusFilterField write FStatusFilterField;
    property DateFilterField: string read FDateFilterField write FDateFilterField;
    property StatusValues: TStringList read FStatusValues;
    property SearchDelay: Integer read FSearchDelay write FSearchDelay;
  end;
var
  frmBaseList: TfrmBaseList;
implementation
uses
  dmg_Main, frm_PrintOptions, FireDAC.Stan.ExprFuncs;
{$R *.dfm}

constructor TfrmBaseList.Create(AOwner: TComponent);
begin
  FStatusValues := TStringList.Create;
  FSearchDelay := 500; // 500 ms debounce para puestos remotos
  FTmrSearch := TTimer.Create(Self);
  FTmrSearch.Enabled := False;
  FTmrSearch.Interval := FSearchDelay;
  FTmrSearch.OnTimer := OnSearchTimer;
  inherited Create(AOwner);
end;

procedure TfrmBaseList.FormShow(Sender: TObject);
begin
  TUIAutoScaler.RegisterForm(Self);
  TUIAutoScaler.ScaleForm(Self);

  if not Assigned(FQryMain) then
  begin
    FQryMain := TFDQuery.Create(Self);
    FQryMain.Connection := dmgMain.dbConn;
    dsMain.DataSet := FQryMain;
  end;
  FQryMain.FetchOptions.Mode := fmOnDemand;
  FQryMain.FetchOptions.RowsetSize := 300;
  FQryMain.FetchOptions.RecordCountMode := cmVisible;
  FQryMain.AfterOpen := QryMainAfterOpen;
  FQryMain.FilterOptions := [foCaseInsensitive];
  
  if not Assigned(dbGridMain.OnDrawColumnCell) then
    dbGridMain.OnDrawColumnCell := dbGridMainDrawColumnCell;
    
  dbGridMain.Options := dbGridMain.Options + [dgMultiSelect, dgRowSelect];

  btnHelp.OnClick := btnHelpClick;

  // Inicializar paneles de filtrado lateral y alfabético
  if FAlphaFilterField <> '' then
  begin
    pnlAlphaFilter.Visible := True;
    CreateAlphaFilterButtons;
  end
  else
    pnlAlphaFilter.Visible := False;

  if (FStatusFilterField <> '') or (FDateFilterField <> '') then
  begin
    pnlLeftFilter.Visible := True;
    
    if FStatusFilterField <> '' then
    begin
      grpStatuses.Visible := True;
      CreateStatusCheckboxes;
    end
    else
      grpStatuses.Visible := False;

    if FDateFilterField <> '' then
    begin
      grpDates.Visible := True;
      dtpStart.Date := Date;
      dtpEnd.Date := Date;
    end
    else
      grpDates.Visible := False;
  end
  else
    pnlLeftFilter.Visible := False;

  if Assigned(edtSearch) then
    edtSearch.OnChange := edtSearchChange;

  TAppTheme.ApplyToForm(Self);
  TAppTheme.ApplyUpperCaseToEdits(Self);

  // IMPORTANTE: Restaurar CharCase del campo de búsqueda a ecNormal.
  // La API ES_UPPERCASE de Windows (usada por ecUpperCase) corrompe
  // caracteres especiales como Ñ, Ç y vocales con tilde en la entrada de texto.
  if Assigned(edtSearch) then
    edtSearch.CharCase := ecNormal;

  btnRefreshClick(nil);
end;

procedure TfrmBaseList.dbGridMainDrawColumnCell(Sender: TObject; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
  Checked: Boolean;
  DrawRect: TRect;
  DrawState: Cardinal;
  Grid: TDBGrid;
  IsOddRow: Boolean;
  LText: string;
  LFlags: Cardinal;
begin
  Grid := TDBGrid(Sender);

  // Aplicar color de fondo alterno si la fila no está seleccionada
  if not (gdSelected in State) then
  begin
    if Assigned(Grid.DataSource) and Assigned(Grid.DataSource.DataSet) and Grid.DataSource.DataSet.Active then
      IsOddRow := Odd(Grid.DataSource.DataSet.RecNo)
    else
      IsOddRow := False;

    if IsOddRow then
      Grid.Canvas.Brush.Color := TAppTheme.COLOR_GRID_ROW_ODD
    else
      Grid.Canvas.Brush.Color := TAppTheme.COLOR_GRID_ROW_EVEN;
  end;

  if Assigned(Column.Field) and 
     ((Column.Field is TBooleanField) or 
      (Pos('activo', LowerCase(Column.Field.FieldName)) > 0) or 
      (Pos('active', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('compartido', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('compartida', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('bloquear', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('bloqueado', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('asegurado', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('comprobado', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('consolidado', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('anulado', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('anulada', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('validado', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('puede_', LowerCase(Column.Field.FieldName)) > 0) or
      (Pos('permiso', LowerCase(Column.Field.FieldName)) > 0)) then
  begin
    Grid.Canvas.Font.Color := Grid.Canvas.Brush.Color;
    Grid.Canvas.FillRect(Rect);
      
    // Force boolean fields to have empty display values so they never render text
    if (Column.Field is TBooleanField) and (TBooleanField(Column.Field).DisplayValues <> ';') then
      TBooleanField(Column.Field).DisplayValues := ';';
    
    if Column.Field is TBooleanField then
      Checked := Column.Field.AsBoolean
    else
    begin
      var LValInt: Integer;
      var LValStr := Trim(Column.Field.AsString);
      if TryStrToInt(LValStr, LValInt) then
        Checked := (LValInt <> 0)
      else
        Checked := SameText(LValStr, 'S') or 
                   SameText(LValStr, 'T') or 
                   SameText(LValStr, 'True') or
                   SameText(LValStr, '1');
    end;
                 
    DrawRect := Rect;
    DrawRect.Left := Rect.Left + (Rect.Width - 14) div 2;
    DrawRect.Right := DrawRect.Left + 14;
    DrawRect.Top := Rect.Top + (Rect.Height - 14) div 2;
    DrawRect.Bottom := DrawRect.Top + 14;
    
    DrawState := DFCS_BUTTONCHECK;
    if Checked then
      DrawState := DrawState or DFCS_CHECKED;
      
    DrawFrameControl(Grid.Canvas.Handle, DrawRect, DFC_BUTTON, DrawState);
  end
  else if Assigned(Column.Field) and Column.Field.IsBlob then
  begin
    Grid.Canvas.FillRect(Rect);
    LText := Column.Field.AsString;
    DrawRect := Rect;
    InflateRect(DrawRect, -2, 0);
    LFlags := DT_SINGLELINE or DT_VCENTER or DT_NOPREFIX;
    if Column.Alignment = taRightJustify then
      LFlags := LFlags or DT_RIGHT
    else if Column.Alignment = taCenter then
      LFlags := LFlags or DT_CENTER
    else
      LFlags := LFlags or DT_LEFT;
    DrawText(Grid.Canvas.Handle, PChar(LText), Length(LText), DrawRect, LFlags);
  end
  else
  begin
    Grid.DefaultDrawColumnCell(Rect, DataCol, Column, State);
  end;
end;
procedure TfrmBaseList.btnRefreshClick(Sender: TObject);
begin
  // Base implementation: override in subclasses to fetch actual data
end;
procedure TfrmBaseList.btnNewClick(Sender: TObject);
begin
  // Base implementation: override in subclasses
end;
procedure TfrmBaseList.btnEditClick(Sender: TObject);
begin
  // Base implementation: override in subclasses
end;
procedure TfrmBaseList.btnDeleteClick(Sender: TObject);
begin
  // Base implementation: override in subclasses
end;

procedure TfrmBaseList.btnPrintClick(Sender: TObject);
begin
  TfrmPrintOptions.ShowOptions(Self, nil, dbGridMain, Self.Caption);
end;

procedure TfrmBaseList.btnHelpClick(Sender: TObject);
var
  LBaseDir, LHelpFilePath: string;
  LHelpLines: TStringList;
begin
  LBaseDir := ExtractFilePath(ParamStr(0));
  LHelpFilePath := LBaseDir + 'help\' + Self.ClassName + '.txt';
  
  if FileExists(LHelpFilePath) then
  begin
    LHelpLines := TStringList.Create;
    try
      LHelpLines.LoadFromFile(LHelpFilePath, TEncoding.UTF8);
      ShowMessage(LHelpLines.Text);
    finally
      LHelpLines.Free;
    end;
  end
  else
  begin
    ShowMessage('Manual de ayuda para ' + Self.Caption + '.' + #13#10 +
                'No se encontró un archivo de ayuda específico para ' + Self.ClassName + '.' + #13#10 +
                'Por favor, póngase en contacto con soporte técnico si requiere asistencia.');
  end;
end;

procedure TfrmBaseList.btnSearchClick(Sender: TObject);
begin
  if Assigned(FTmrSearch) then
    FTmrSearch.Enabled := False;
  DoSearch;
end;

procedure TfrmBaseList.edtSearchKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
  begin
    Key := 0; // Prevent beep
    if Assigned(FTmrSearch) then
      FTmrSearch.Enabled := False;
    DoSearch;
  end;
end;

procedure TfrmBaseList.edtSearchChange(Sender: TObject);
begin
  if Assigned(FTmrSearch) then
  begin
    FTmrSearch.Enabled := False;
    FTmrSearch.Interval := FSearchDelay;
    FTmrSearch.Enabled := True;
  end
  else
    DoSearch;
end;

procedure TfrmBaseList.OnSearchTimer(Sender: TObject);
begin
  if Assigned(FTmrSearch) then
    FTmrSearch.Enabled := False;
  DoSearch;
end;

procedure TfrmBaseList.DoSearch;
begin
  if Assigned(FTmrSearch) then
    FTmrSearch.Enabled := False;
  btnRefreshClick(nil);
end;

procedure TfrmBaseList.dbGridMainDblClick(Sender: TObject);
begin
  btnEditClick(nil);
end;


procedure TfrmBaseList.dbGridMainTitleClick(Column: TColumn);
begin
  if not Assigned(FQryMain) or not FQryMain.Active then
    Exit;
  if FLastSortColumn = Column.FieldName then
  begin
    if FLastSortDirection = 'A' then
      FLastSortDirection := 'D'
    else
      FLastSortDirection := 'A';
  end
  else
  begin
    FLastSortColumn := Column.FieldName;
    FLastSortDirection := 'A';
  end;
  if FLastSortDirection = 'D' then
    FQryMain.IndexFieldNames := Column.FieldName + ':D'
  else
    FQryMain.IndexFieldNames := Column.FieldName + ':A';
end;

procedure TfrmBaseList.QryMainAfterOpen(DataSet: TDataSet);
begin
  ApplyAllFilters;
  dbGridMain.AutoFitColumns;
end;

destructor TfrmBaseList.Destroy;
begin
  if Assigned(FTmrSearch) then
  begin
    FTmrSearch.Enabled := False;
    FreeAndNil(FTmrSearch);
  end;
  TUIAutoScaler.UnregisterForm(Self);
  if Assigned(FStatusValues) then
    FreeAndNil(FStatusValues);
  inherited Destroy;
end;

procedure TfrmBaseList.CreateAlphaFilterButtons;
var
  I: Integer;
  Btn: TSpeedButton;
const
  Letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
begin
  for I := pnlAlphaFilter.ControlCount - 1 downto 0 do
    if pnlAlphaFilter.Controls[I] is TSpeedButton then
      pnlAlphaFilter.Controls[I].Free;

  // Botones A-Z en orden inverso para que al alinearlos al Top queden en orden A-Z de arriba a abajo
  for I := Length(Letters) downto 1 do
  begin
    Btn := TSpeedButton.Create(Self);
    Btn.Parent := pnlAlphaFilter;
    Btn.Align := alTop;
    Btn.Height := 13;
    Btn.Caption := Letters[I];
    Btn.Flat := True;
    Btn.GroupIndex := 10;
    Btn.OnClick := AlphaFilterClick;
    Btn.Font.Size := 8;
  end;

  Btn := TSpeedButton.Create(Self);
  Btn.Parent := pnlAlphaFilter;
  Btn.Align := alTop;
  Btn.Height := 15;
  Btn.Caption := '0-9';
  Btn.Flat := True;
  Btn.GroupIndex := 10;
  Btn.OnClick := AlphaFilterClick;
  Btn.Font.Size := 8;

  Btn := TSpeedButton.Create(Self);
  Btn.Parent := pnlAlphaFilter;
  Btn.Align := alTop;
  Btn.Height := 15;
  Btn.Caption := '*';
  Btn.Flat := True;
  Btn.GroupIndex := 10;
  Btn.Down := True;
  Btn.OnClick := AlphaFilterClick;
  Btn.Font.Size := 9;
  Btn.Font.Style := [fsBold];
end;

procedure TfrmBaseList.CreateStatusCheckboxes;
var
  I: Integer;
  Cmb: TComboBox;
begin
  for I := grpStatuses.ControlCount - 1 downto 0 do
    grpStatuses.Controls[I].Free;

  if not Assigned(FStatusValues) or (FStatusValues.Count = 0) then
  begin
    grpStatuses.Visible := False;
    Exit;
  end;

  grpStatuses.Visible := True;
  grpStatuses.Caption := 'Estado';

  Cmb := TComboBox.Create(Self);
  Cmb.Name := 'cmbStatusFilter';
  Cmb.Parent := grpStatuses;
  Cmb.Align := alTop;
  Cmb.Style := csDropDownList;
  Cmb.Items.Add('TODOS');

  for I := 0 to FStatusValues.Count - 1 do
    Cmb.Items.Add(FStatusValues[I]);

  Cmb.ItemIndex := 0; // 'TODOS' por defecto
  Cmb.OnChange := StatusFilterClick;

  grpStatuses.Height := 55;
end;

procedure TfrmBaseList.AlphaFilterClick(Sender: TObject);
var
  Btn: TSpeedButton;
begin
  if not (Sender is TSpeedButton) then Exit;
  Btn := TSpeedButton(Sender);
  Btn.Down := True;

  if Btn.Caption = '*' then
    FSelectedLetter := ''
  else
    FSelectedLetter := Btn.Caption;

  ApplyAllFilters;
end;

procedure TfrmBaseList.DateFilterClick(Sender: TObject);
begin
  ApplyAllFilters;
end;

procedure TfrmBaseList.StatusFilterClick(Sender: TObject);
begin
  ApplyAllFilters;
end;

procedure TfrmBaseList.dtpChange(Sender: TObject);
begin
  rbDateCustom.Checked := True;
  ApplyAllFilters;
end;

function TfrmBaseList.IsBoolFilterField(const AFieldName: string): Boolean;
var
  LFld: TField;
begin
  if not Assigned(FQryMain) then Exit(True);
  LFld := FQryMain.FindField(AFieldName);
  if LFld <> nil then
    Result := (LFld is TBooleanField)
  else
    Result := SameText(AFieldName, 'activo') or SameText(AFieldName, 'active') or
              SameText(AFieldName, 'facturado') or SameText(AFieldName, 'pagada') or
              SameText(AFieldName, 'pagado') or SameText(AFieldName, 'comprobado') or
              SameText(AFieldName, 'consolidado');
end;

procedure TfrmBaseList.ApplyAllFilters;
var
  I: Integer;
  FilterParts: TStringList;
  SearchPart, DatePart, FinalFilter: string;
  DateLimit: TDateTime;
begin
  if not Assigned(FQryMain) then Exit;

  FilterParts := TStringList.Create;
  try
    // 1. Filtro Alfabético
    if (FAlphaFilterField <> '') and (FSelectedLetter <> '') then
    begin
      if FSelectedLetter = '0-9' then
        FilterParts.Add('(' + FAlphaFilterField + ' >= ''0'') AND (' + FAlphaFilterField + ' <= ''9'')')
      else
        FilterParts.Add(FAlphaFilterField + ' LIKE ' + QuotedStr(FSelectedLetter + '%'));
    end;

    // 2. Filtro de Estados
    if FStatusFilterField <> '' then
    begin
      var CmbStatus: TComboBox := nil;
      for I := 0 to grpStatuses.ControlCount - 1 do
      begin
        if grpStatuses.Controls[I] is TComboBox then
        begin
          CmbStatus := TComboBox(grpStatuses.Controls[I]);
          Break;
        end;
      end;

      if Assigned(CmbStatus) and (CmbStatus.ItemIndex > 0) then
      begin
        var LSelectedStatus := CmbStatus.Items[CmbStatus.ItemIndex];

        if SameText(FStatusFilterField, 'comprobado_consolidado') then
        begin
          if SameText(LSelectedStatus, 'Validado') then
            FilterParts.Add('(comprobado = True)')
          else if SameText(LSelectedStatus, 'No Validado') then
            FilterParts.Add('(comprobado = False OR comprobado IS NULL)')
          else if SameText(LSelectedStatus, 'Consolidado') then
            FilterParts.Add('(consolidado = True)')
          else if SameText(LSelectedStatus, 'No Consolidado') then
            FilterParts.Add('(consolidado = False OR consolidado IS NULL)');
        end
        else if SameText(FStatusFilterField, 'activo') or SameText(FStatusFilterField, 'active') then
        begin
          if SameText(LSelectedStatus, 'Activo') or SameText(LSelectedStatus, 'Active') then
          begin
            if IsBoolFilterField(FStatusFilterField) then
              FilterParts.Add('(' + FStatusFilterField + ' = True)')
            else
              FilterParts.Add('(' + FStatusFilterField + ' = 1)');
          end
          else if SameText(LSelectedStatus, 'Inactivo') or SameText(LSelectedStatus, 'Inactive') then
          begin
            if IsBoolFilterField(FStatusFilterField) then
              FilterParts.Add('(' + FStatusFilterField + ' = False OR ' + FStatusFilterField + ' IS NULL)')
            else
              FilterParts.Add('(' + FStatusFilterField + ' = 0 OR ' + FStatusFilterField + ' IS NULL)');
          end
          else
            FilterParts.Add('(' + FStatusFilterField + ' = ' + QuotedStr(LSelectedStatus) + ')');
        end
        else if SameText(FStatusFilterField, 'facturado') then
        begin
          if SameText(LSelectedStatus, 'Facturado') then
          begin
            if IsBoolFilterField(FStatusFilterField) then
              FilterParts.Add('(facturado = True)')
            else
              FilterParts.Add('(facturado = 1)');
          end
          else if SameText(LSelectedStatus, 'Pendiente') then
          begin
            if IsBoolFilterField(FStatusFilterField) then
              FilterParts.Add('(facturado = False OR facturado IS NULL)')
            else
              FilterParts.Add('(facturado = 0 OR facturado IS NULL)');
          end
          else
            FilterParts.Add('(facturado = ' + QuotedStr(LSelectedStatus) + ')');
        end
        else if SameText(FStatusFilterField, 'pagada') or SameText(FStatusFilterField, 'pagado') then
        begin
          if SameText(LSelectedStatus, 'Pagada') or SameText(LSelectedStatus, 'Pagado') then
          begin
            if IsBoolFilterField(FStatusFilterField) then
              FilterParts.Add('(' + FStatusFilterField + ' = True)')
            else
              FilterParts.Add('(' + FStatusFilterField + ' = 1)');
          end
          else if SameText(LSelectedStatus, 'Pendiente') then
          begin
            if IsBoolFilterField(FStatusFilterField) then
              FilterParts.Add('(' + FStatusFilterField + ' = False OR ' + FStatusFilterField + ' IS NULL)')
            else
              FilterParts.Add('(' + FStatusFilterField + ' = 0 OR ' + FStatusFilterField + ' IS NULL)');
          end
          else
            FilterParts.Add('(' + FStatusFilterField + ' = ' + QuotedStr(LSelectedStatus) + ')');
        end
        else
        begin
          FilterParts.Add('(' + FStatusFilterField + ' = ' + QuotedStr(LSelectedStatus) + ')');
        end;
      end;
      // Si CmbStatus.ItemIndex = 0 ('TODOS'), no se añade restriccion de estado
    end;

    // 3. Filtro de Fechas
    if FDateFilterField <> '' then
    begin
      DatePart := '';
      if rbDateToday.Checked then
      begin
        DateLimit := Date;
        DatePart := FDateFilterField + ' >= {d ' + FormatDateTime('yyyy-mm-dd', DateLimit) + '}';
      end
      else if rbDateWeek.Checked then
      begin
        DateLimit := Date - 7;
        DatePart := FDateFilterField + ' >= {d ' + FormatDateTime('yyyy-mm-dd', DateLimit) + '}';
      end
      else if rbDateMonth.Checked then
      begin
        DateLimit := IncMonth(Date, -1);
        DatePart := FDateFilterField + ' >= {d ' + FormatDateTime('yyyy-mm-dd', DateLimit) + '}';
      end
      else if rbDateCustom.Checked then
      begin
        DatePart := '(' + FDateFilterField + ' >= {d ' + FormatDateTime('yyyy-mm-dd', Trunc(dtpStart.Date)) + '}' +
                    ' AND ' + FDateFilterField + ' < {d ' + FormatDateTime('yyyy-mm-dd', Trunc(dtpEnd.Date) + 1) + '})';
      end;
      
      if DatePart <> '' then
        FilterParts.Add(DatePart);
    end;

    if FilterParts.Count > 0 then
    begin
      FinalFilter := FilterParts[0];
      for I := 1 to FilterParts.Count - 1 do
        FinalFilter := FinalFilter + ' AND ' + FilterParts[I];
        
      FQryMain.Filter := FinalFilter;
      FQryMain.Filtered := True;
    end
    else
    begin
      FQryMain.Filter := '';
      FQryMain.Filtered := False;
    end;

    lblRecordCount.Caption := Format('Registros: %d', [FQryMain.RecordCount]);
  finally
    FilterParts.Free;
  end;
end;

function TfrmBaseList.GetSelectedOrCurrentIds(const AIdFieldName: string = 'id'): TArray<string>;
var
  LList: TList<string>;
  LBookmark: TBookmark;
  I: Integer;
begin
  SetLength(Result, 0);
  if not Assigned(FQryMain) or not FQryMain.Active or FQryMain.IsEmpty then
    Exit;

  LList := TList<string>.Create;
  try
    if dbGridMain.SelectedRows.Count > 1 then
    begin
      FQryMain.DisableControls;
      LBookmark := FQryMain.GetBookmark;
      try
        for I := 0 to dbGridMain.SelectedRows.Count - 1 do
        begin
          FQryMain.GotoBookmark(dbGridMain.SelectedRows[I]);
          if (FQryMain.FindField(AIdFieldName) <> nil) and (FQryMain.FieldByName(AIdFieldName).AsString <> '') then
            LList.Add(FQryMain.FieldByName(AIdFieldName).AsString);
        end;
      finally
        if FQryMain.BookmarkValid(LBookmark) then
          FQryMain.GotoBookmark(LBookmark);
        FQryMain.FreeBookmark(LBookmark);
        FQryMain.EnableControls;
      end;
    end
    else
    begin
      if (FQryMain.FindField(AIdFieldName) <> nil) and (FQryMain.FieldByName(AIdFieldName).AsString <> '') then
        LList.Add(FQryMain.FieldByName(AIdFieldName).AsString);
    end;

    Result := LList.ToArray;
  finally
    LList.Free;
  end;
end;

end.
