bogdan / datagrid

Gem to create tables grids with sortable columns and filters
MIT License
1.02k stars 115 forks source link

Totals table: Show all filters as rows #286

Closed Ellerden closed 4 years ago

Ellerden commented 4 years ago

I use filters in grid (e.g. contacted/replied/canceled) and the last filter is 'Totals' - which basically should show all other filters at once as rows in one grid. Is it possible to do so? Or maybe there's a way to include a grid inside a grid when choosing a filter?

Here's what I mean — the usual, let's say 'contacted' filter is shown

 ____________ | 1 June | 2 June | 3 June | 4 June |
John             |.     0           ....            ....            1
Erick             |.      0           ....           ....            1 
Anna             |.      0          ....             ...            1 

So I should have smth like this in the end.

____________ | 1 June | 2 June | 3 June | 4 June |
contacted  |.     0           ....            ....            3
replied       |.      1           ....           ....             ....  
canceled.  |.      1          ....             5               1 
bogdan commented 4 years ago

Give me your current grid source code and I'll show how to add a "Total" row.

Ellerden commented 4 years ago

I already have a total row added it in the controller:

    @stats_grid = StatsGrid.new(params[:stats_grid])
    @total_by_day_row = ['Total', *([0] * 8)]

The thing is that I need to have all 'stage-filters' results as rows when 'totals' filter selected.

Here's the grid code:

class StatsGrid
  include Datagrid

  #
  # Scope
  #

  scope do
    Team.left_joins(recruiters: { user: :events })
        .where('events.type': %w[placement_added placement_stage_changed])
        .where.not('events.placement_id': nil)
        .group('teams.id')
        .order(:name)
  end

  #
  # Filters
  #

  filter(
    :status,
    :enum,
    select: Placement::STATS_STAGES.map { |v| [v.humanize, v] },
    default: 'contacted',
    include_blank: false
  ) do |stage|
    query = if stage == 'sourced'
              where("events.type = 'placement_added' OR events.properties->>'to' = 'sourced'")
            else
              where("events.properties->>'to' = ?", stage)
            end
    query
  end

  filter(
    :performed_at,
    :date,
    default: Time.zone.now.beginning_of_week
  ) do |first_day_of_week, scope|
    query = []
    week = first_day_of_week.all_week
    week.each do |day|
      query.push(
        "count(events.id) filter (where CAST(events.performed_at AS DATE) = ?) AS #{
          day.strftime('%A')}"
      )
    end
    scope.select(
      "teams.*, count(events.id) AS total,
      #{ActiveRecord::Base.sanitize_sql_array([query.join(', '), *week.to_a])}"
    ).where('events.performed_at': first_day_of_week..first_day_of_week + 1.week)
  end

  #
  # Columns
  #

  column(:name) do |team, grid|
    format(true) do
      link_to team.name, display_team_path(
        team.id,
        :stats_by_recruiter,
        team_stats_by_recruiter_grid: {
          status: grid.status,
          performed_at: grid.performed_at
        }
      )
    end
  end

  dynamic do |grid|
    performed_at.all_week.each do |day|
      column(day.strftime('%A') + " #{day.day}", html: true) do |team|
          team[day.strftime('%A').downcase]
      end
    end
  end

    column('total')
  end
end

Placement::STATS_STAGES that are used as filters:

STATS_STAGES = %w[sourced contacted replied interviewed totals].freeze
bogdan commented 4 years ago

Here is what you can do:

class StatsGrid

  dynamic do |grid|
    performed_at.all_week.each do |day|
      column(grid.date_column_name(day), header: day.strftime('%A') + " #{day.day}") do |team|
          format(team[day.strftime('%A').downcase) do |value|
            value # I am not sure why the column is html only in your code - apply your formatting if any
      end
    end
  end

  def date_column_name(date)
    :"day_#{date.strftime("%A").downcase}"
  end

  def total_row 
    ['Total'] + performed_at.all_weeks.map do |day|
       assets.sum {|row| data_value(row, date_column_name(day))}
  end
end

View code (HAML for simplicity):

%table
  = datagrid_header(@grid)
  = datagrid_rows(@grid)
  -# you can define a helper like datagrid_total_row if you want
  %tr
    - @grid.total_row.each do |value|
      %td= value
Ellerden commented 4 years ago

Thanks, for the answer. Also, is it possible to show array of constants as a column? I'd like to display each filter of STATS_STAGES as a row in column 'Name'. STATS_STAGES = %w[sourced contacted replied interviewed totals].freeze

bogdan commented 4 years ago

I am not sure why would you have problems with as you are already familiar with dynamic. Please explain in more clearly.

Ellerden commented 4 years ago

Sorry, got it figured out) Thanks for your help