unit orm_search;



interface



uses

  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, StrUtils,

  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, 

  System.Generics.Collections, Vcl.StdCtrls, Vcl.ExtCtrls, scGPExtControls,

  scControls, scGPFontControls, scGPControls, sPanel, AdvUtil, AdvGrid, 

  AdvCGrid, Vcl.ComCtrls, Vcl.WinXPickers, orm_utils, ORM_CustomAttributes,

  System.Rtti, System.DateUtils, DM1, Data.DB, Uni, Vcl.Grids, AdvObj,

  BaseGrid, Vcl.Mask, interposer_class;



type

  TActiveFilter = class

  private

    FFieldName: string;

    FDisplayName: string;

    FFilterOperator: string;

    FValue1: string;

    FValue2: string;

    FFilterPanel: TscGPPanel;

    FFilterLabel: TLabel;

    FDeleteButton: TscGPCharGlyphButton;

  public

    property FieldName: string read FFieldName write FFieldName;

    property DisplayName: string read FDisplayName write FDisplayName;

    property FilterOperator: string read FFilterOperator write FFilterOperator;

    property Value1: string read FValue1 write FValue1;

    property Value2: string read FValue2 write FValue2;

    property FilterPanel: TscGPPanel read FFilterPanel write FFilterPanel;

    property FilterLabel: TLabel read FFilterLabel write FFilterLabel;

    property DeleteButton: TscGPCharGlyphButton read FDeleteButton write FDeleteButton;

    

    destructor Destroy; override;

  end;



  TORMSearchForm = class(TForm)

    pnlTop: TPanel;

    pnlFilters: TscGPPanel;

    pnlActiveFilters: TscGPPanel;

    pnlGrid: TsPanel;

    pnlBottom: TPanel;

    

    cboField: TscComboBox;

    cboOperator: TscComboBox;

    edtValue1: TscGPEdit;

    edtValue2: TscGPEdit;

    dateValue1: TscGPDateEdit;

    dateValue2: TscGPDateEdit;

    btnAddFilter: TscGPCharGlyphButton;

    

    SearchGrid: TAdvColumnGrid;

    

    btnSearch: TscGPCharGlyphButton;

    btnClose: TscGPCharGlyphButton;

    btnClearFilters: TscGPCharGlyphButton;

    

    procedure FormCreate(Sender: TObject);

    procedure FormDestroy(Sender: TObject);

    procedure cboFieldChange(Sender: TObject);

    procedure cboOperatorChange(Sender: TObject);

    procedure btnAddFilterClick(Sender: TObject);

    procedure btnSearchClick(Sender: TObject);

    procedure btnCloseClick(Sender: TObject);

    procedure btnClearFiltersClick(Sender: TObject);

    procedure SearchGridDblClick(Sender: TObject);

    procedure FilterDeleteButtonClick(Sender: TObject);



  private

    FActiveFilters: TObjectList<TActiveFilter>;

    

    procedure LoadMockData;

    procedure UpdateValueControls;

    procedure AddActiveFilter;

    procedure RemoveActiveFilter(Filter: TActiveFilter);

    procedure ClearActiveFilters;

    procedure UpdatePanelLayouts;

    procedure AdjustFilterPanelHeight;

    procedure CreateFilterPanel(Filter: TActiveFilter);

    

  public

    constructor Create(AOwner: TComponent); override;

    destructor Destroy; override;

  end;



var

  ORMSearchForm: TORMSearchForm;



implementation



uses Math;



{$R *.dfm}



const

  FILTER_PANEL_HEIGHT = 35;  // Height of each filter panel

  FILTER_PANEL_SPACING = 5;  // Spacing between filter panels

  MIN_GRID_HEIGHT = 200;     // Minimum height for the grid



constructor TORMSearchForm.Create(AOwner: TComponent);

begin

  inherited;

  // Initial setup

  pnlActiveFilters.Visible := False;

  pnlActiveFilters.Height := 0;

  FActiveFilters := TObjectList<TActiveFilter>.Create(True);



  // Ensure the grid takes up the space initially

  pnlGrid.Align := alClient;

  SearchGrid.Align := alClient;

end;



destructor TORMSearchForm.Destroy;

begin

  FActiveFilters.Free;

  inherited;

end;



procedure TORMSearchForm.FormCreate(Sender: TObject);

begin

  LoadMockData;

end;



procedure TORMSearchForm.LoadMockData;

begin

  // Mock field list

  cboField.Items.Clear;

  cboField.Items.AddStrings([

    'Product Name',

    'Category',

    'Price',

    'Stock',

    'Last Order Date'

  ]);

  cboField.ItemIndex := 0;

  

  // Mock grid data

  SearchGrid.ColCount := 5;

  SearchGrid.RowCount := 5;

  

  // Headers

  SearchGrid.Cells[0,0] := 'Product Name';

  SearchGrid.Cells[1,0] := 'Category';

  SearchGrid.Cells[2,0] := 'Price';

  SearchGrid.Cells[3,0] := 'Stock';

  SearchGrid.Cells[4,0] := 'Last Order';

  

  // Sample data

  SearchGrid.Cells[0,1] := 'Laptop';

  SearchGrid.Cells[1,1] := 'Electronics';

  SearchGrid.Cells[2,1] := '999.99';

  SearchGrid.Cells[3,1] := '50';

  SearchGrid.Cells[4,1] := '10/01/2024';

  

  SearchGrid.Cells[0,2] := 'Mouse';

  SearchGrid.Cells[1,2] := 'Electronics';

  SearchGrid.Cells[2,2] := '29.99';

  SearchGrid.Cells[3,2] := '100';

  SearchGrid.Cells[4,2] := '15/01/2024';

  

  SearchGrid.Cells[0,3] := 'Desk';

  SearchGrid.Cells[1,3] := 'Furniture';

  SearchGrid.Cells[2,3] := '299.99';

  SearchGrid.Cells[3,3] := '25';

  SearchGrid.Cells[4,3] := '20/01/2024';

  

  SearchGrid.Cells[0,4] := 'Chair';

  SearchGrid.Cells[1,4] := 'Furniture';

  SearchGrid.Cells[2,4] := '199.99';

  SearchGrid.Cells[3,4] := '30';

  SearchGrid.Cells[4,4] := '25/01/2024';

  

  // Configure column widths

  SearchGrid.ColWidths[0] := 200; // Product Name

  SearchGrid.ColWidths[1] := 150; // Category

  SearchGrid.ColWidths[2] := 100; // Price

  SearchGrid.ColWidths[3] := 100; // Stock

  SearchGrid.ColWidths[4] := 150; // Last Order

  

  cboFieldChange(nil);

end;



procedure TORMSearchForm.cboFieldChange(Sender: TObject);

begin

  cboOperator.Items.Clear;

  edtValue1.Clear;

  edtValue2.Clear;

  

  case cboField.ItemIndex of

    0, 1: // Product Name, Category

      begin

        cboOperator.Items.AddStrings(['Equals', 'Contains', 'Starts with']);

        edtValue1.Visible := True;

        edtValue2.Visible := False;

        dateValue1.Visible := False;

        dateValue2.Visible := False;

      end;

    2, 3: // Price, Stock

      begin

        cboOperator.Items.AddStrings(['Equals', 'Greater than', 'Less than', 'Between']);

        edtValue1.Visible := True;

        edtValue2.Visible := False;

        dateValue1.Visible := False;

        dateValue2.Visible := False;

      end;

    4: // Last Order Date

      begin

        cboOperator.Items.AddStrings(['Equals', 'Greater than', 'Less than', 'Between']);

        edtValue1.Visible := False;

        edtValue2.Visible := False;

        dateValue1.Visible := True;

        dateValue2.Visible := False;

      end;

  end;

  

  cboOperator.ItemIndex := 0;

  UpdateValueControls;

end;



procedure TORMSearchForm.cboOperatorChange(Sender: TObject);

begin

  UpdateValueControls;

end;



procedure TORMSearchForm.UpdateValueControls;

var

  IsBetween: Boolean;

begin

  IsBetween := cboOperator.Text = 'Between';

  

  case cboField.ItemIndex of

    4: // Date field

      begin

        dateValue1.Visible := True;

        dateValue2.Visible := IsBetween;

        edtValue1.Visible := False;

        edtValue2.Visible := False;

      end;

    else

      begin

        dateValue1.Visible := False;

        dateValue2.Visible := False;

        edtValue1.Visible := True;

        edtValue2.Visible := IsBetween;

      end;

  end;

end;



procedure TORMSearchForm.RemoveActiveFilter(Filter: TActiveFilter);

begin

  Filter.FilterPanel.Parent := nil;

  FActiveFilters.Remove(Filter);

  UpdatePanelLayouts;

end;



procedure TORMSearchForm.ClearActiveFilters;

begin

  FActiveFilters.Clear;

end;



procedure TORMSearchForm.FilterDeleteButtonClick(Sender: TObject);

begin

  RemoveActiveFilter(TActiveFilter(TComponent(Sender).Tag));

end;



procedure TORMSearchForm.btnAddFilterClick(Sender: TObject);

begin

  AddActiveFilter;

end;



procedure TORMSearchForm.btnSearchClick(Sender: TObject);

begin

  ShowMessage('Search clicked - In mockup version, grid data is static');

end;



procedure TORMSearchForm.btnClearFiltersClick(Sender: TObject);

begin

  ClearActiveFilters;

end;



procedure TORMSearchForm.btnCloseClick(Sender: TObject);

begin

  Close;

end;



procedure TORMSearchForm.SearchGridDblClick(Sender: TObject);

begin

  if SearchGrid.Row > 0 then

    ShowMessage(Format('Selected: %s', [SearchGrid.Cells[0, SearchGrid.Row]]));

end;



procedure TORMSearchForm.FormDestroy(Sender: TObject);

begin

  ClearActiveFilters;

end;



destructor TActiveFilter.Destroy;

begin

  if Assigned(FFilterPanel) then

    FFilterPanel.Free;

  inherited;

end;



procedure TORMSearchForm.CreateFilterPanel(Filter: TActiveFilter);

begin

  // Create the panel container

  Filter.FilterPanel := TscGPPanel.Create(pnlActiveFilters);

  with Filter.FilterPanel do

  begin

    Parent := pnlActiveFilters;

    Align := alTop;

    Height := FILTER_PANEL_HEIGHT;

    Margins.SetBounds(5, FILTER_PANEL_SPACING, 5, FILTER_PANEL_SPACING);

    BevelOuter := bvNone;



    // Set panel styling

    FillColor := clWhite;

    FillColorAlpha := 200;

    FrameWidth := 1;

    FrameColor := clGray;

    FrameColorAlpha := 70;

    FrameRadius := 5;

  end;



  // Create the filter description label

  Filter.FilterLabel := TLabel.Create(Filter.FilterPanel);

  with Filter.FilterLabel do

  begin

    Parent := Filter.FilterPanel;

    Align := alLeft;

    AutoSize := True;

    Margins.Left := 10;

    Margins.Top := 8;

    Caption := Format('%s %s %s%s',

      [Filter.DisplayName, Filter.FilterOperator, Filter.Value1,

       IfThen(Filter.Value2 <> '', ' AND ' + Filter.Value2, '')]);

  end;



  // Create the delete button

  Filter.DeleteButton := TscGPCharGlyphButton.Create(Filter.FilterPanel);

  with Filter.DeleteButton do

  begin

    Parent := Filter.FilterPanel;

    Align := alRight;

    Width := 24;

    Margins.Right := 5;

    Margins.Top := 5;

    Margins.Bottom := 5;

    GlyphOptions.Index := 13;

    OnClick := FilterDeleteButtonClick;

    Tag := NativeInt(Filter);

  end;

end;



procedure TORMSearchForm.AdjustFilterPanelHeight;

var

  TotalFilterHeight: Integer;

  RequiredHeight: Integer;

  AvailableSpace: Integer;

begin

  if FActiveFilters.Count = 0 then

  begin

    pnlActiveFilters.Visible := False;

    pnlActiveFilters.Height := 0;

    Exit;

  end;



  // Make panel visible if this is the first filter

  if not pnlActiveFilters.Visible then

    pnlActiveFilters.Visible := True;



  // Calculate total height needed for all filters

  TotalFilterHeight := FActiveFilters.Count * (FILTER_PANEL_HEIGHT + (FILTER_PANEL_SPACING * 2));



  // Calculate available space

  AvailableSpace := ClientHeight - pnlTop.Height - pnlBottom.Height - MIN_GRID_HEIGHT;



  // Determine required height while maintaining minimum grid size

  RequiredHeight := Min(TotalFilterHeight, AvailableSpace);



  // Update panel height

  pnlActiveFilters.Height := RequiredHeight;

end;



procedure TORMSearchForm.UpdatePanelLayouts;

var

  i: Integer;

  Filter: TActiveFilter;

begin

  // Reposition all filter panels from bottom to top

  for i := FActiveFilters.Count - 1 downto 0 do

  begin

    Filter := FActiveFilters[i];

    Filter.FilterPanel.Top := i * (FILTER_PANEL_HEIGHT + (FILTER_PANEL_SPACING * 2));

  end;



  // Adjust the overall panel height

  AdjustFilterPanelHeight;

end;



procedure TORMSearchForm.AddActiveFilter;

var

  Filter: TActiveFilter;

  Value1, Value2: string;

begin

  if cboField.ItemIndex < 0 then

    Exit;



  if dateValue1.Visible then

    Value1 := DateToStr(dateValue1.Date)

  else

    Value1 := edtValue1.Text;



  if dateValue2.Visible then

    Value2 := DateToStr(dateValue2.Date)

  else

    Value2 := edtValue2.Text;



  if Value1 = '' then

    Exit;

  if (cboOperator.Text = 'Between') and (Value2 = '') then

    Exit;



  Filter := TActiveFilter.Create;

  try

    // Set filter properties

    Filter.FieldName := cboField.Text;

    Filter.DisplayName := cboField.Text;

    Filter.FilterOperator := cboOperator.Text;

    Filter.Value1 := Value1;

    Filter.Value2 := Value2;



    // Create visual components

    CreateFilterPanel(Filter);



    // Add to collection

    FActiveFilters.Add(Filter);



    // Update layouts

    UpdatePanelLayouts;



    // Clear inputs

    edtValue1.Clear;

    edtValue2.Clear;

    dateValue1.Date := Date;

    dateValue2.Date := Date;

    cboField.ItemIndex := 0;

    cboFieldChange(nil);



  except

    Filter.Free;

    raise;

  end;

end;



procedure ShowSearchForm;

begin

  ORMSearchForm := TORMSearchForm.Create(Application);

  try

    ORMSearchForm.ShowModal;

  finally

    ORMSearchForm.Free;

  end;

end;



end.
------------------

Enhance orm_search unit. Instead of mockup data, implement real  funcionality.

The basic class structure of TSearchColumn and TActiveFilter will be adapted to something like this:

type
  TSearchColumn = class
  private
    FFieldName: string;
    FDisplayName: string;
    FWidth: Integer;
    FVisible: Boolean;
    FUsableAsFilter: Boolean; // tells if this field can be used as a filter too
  public
    constructor Create(const AFieldName, ADisplayName: string; AWidth: Integer; AVisible: Boolean = True; AUsableAsFilter: Boolean = True);
    property FieldName: string read FFieldName;
    property DisplayName: string read FDisplayName;
    property Width: Integer read FWidth;
    property Visible: Boolean read FVisible;
    property UsableAsFilter: Boolean read FUsableAsFilter;
  end;

  TActiveFilter = class
  private
    FFieldName: string;
    FDisplayName: string;
    FFilterOperator: string;
    FValue1: string;
    FValue2: string;
    FFilterPanel: TscGPPanel;
    FFilterLabel: TLabel;
    FDeleteButton: TscGPCharGlyphButton;
  public
    property FieldName: string read FFieldName write FFieldName;
    property DisplayName: string read FDisplayName write FDisplayName;
    property FilterOperator: string read FFilterOperator write FFilterOperator;
    property Value1: string read FValue1 write FValue1;
    property Value2: string read FValue2 write FValue2;
    property FilterPanel: TscGPPanel read FFilterPanel write FFilterPanel;
    property FilterLabel: TLabel read FFilterLabel write FFilterLabel;
    property DeleteButton: TscGPCharGlyphButton read FDeleteButton write FDeleteButton;
    destructor Destroy; override;
  end;

-------

It will be used as follows:  

procedure ShowProductSearch;
var
  SearchColumns: array of TSearchColumn;
  var BaseQuerySrch: string;
begin
  SetLength(SearchColumns, 7);
  // BaseQuerySrch(column_name)
  SearchColumns[0] := TSearchColumn.Create('ProductID', 'ID', 80, True, True);
  SearchColumns[1] := TSearchColumn.Create('SKUCode', 'SKU', 100, True, True);
  SearchColumns[2] := TSearchColumn.Create('ProductName', 'Product Name', 200, True, True);
  SearchColumns[3] := TSearchColumn.Create('Category', 'Category', 120, True, True);
  SearchColumns[4] := TSearchColumn.Create('UnitPrice', 'Price', 100, True, False);
  SearchColumns[5] := TSearchColumn.Create('StockQuantity', 'Stock', 80, True, True);
  SearchColumns[6] := TSearchColumn.Create('LastRestockDate', 'Last Restock', 120, True, False);

  BaseQuerySrch:= 'SELECT * FROM products';

  // SelectedInfo will be a class with all the TSearchColumn names and respective (Variant) values of the selected rows of the Search Grid
  var SelectedProduct: TSelectedSearch;

  try
    if TORMSearchForm.Execute(BaseQuerySrch, SearchColumns, SelectedInfo) then
    begin
      // Use SelectedProduct...
      ShowMessage(Format('Selected Info: %s', [SelectedProduct.Value['ProductID'].AsString]));
    end;
  finally
    // Clean up search columns
    for var i := 0 to High(SearchColumns) do
      SearchColumns[i].Free;
  end;
end;

when using a TUniQuery for opening the filtered search, get the field datatypes thru the TUniQuery dataset.

First, on orm_search unit, eliminate all the mockup code.
The filters will be added by the Form UI. 
When the search button is clicked, get the data according to the filters. 
Adapt and enhance the funcionalities as appropriate for a clean and easy usage.
Adapt the orm_search classes as you seem fit. 
Give me the complete unit .pas and .dfm files, without gaps.