Comments

Clickable rows and slim, compact design.

Comments Table

Export CSV

Loading data...

Filters

Table definition app/infotable/comments_table.rb
# ============================================================================
# COMMENTS TABLE - Demonstrates VIRTUAL COLUMNS with filtering
# ============================================================================
#
# This table showcases how to use VIRTUAL (computed) columns for filtering
# and sorting on aggregated data.
#
# VIRTUAL COLUMN EXAMPLE:
# - Column: author_total_comments
# - Type: Subquery (SELECT COUNT(*) ...)
# - Features: Sortable ✅ and Filterable ✅
# - Implementation: Mark as `virtual: true` to enable HAVING clause filtering
#
# See VIRTUAL_COLUMNS_EXAMPLE.md for more examples
# ============================================================================

class CommentsTable < Infotable::Base
  query do
    Comment.select(
      "comments.*",
      "(SELECT COUNT(*) FROM comments AS c WHERE c.author_id = comments.author_id) AS author_total_comments"
    ).includes(:post, :author)
  end

  column :id,
    label: "ID",
    type: :integer,
    width: "60px",
    sortable: true,
    visible: false

  column :post,
    label: "Post",
    type: :string,
    width: "300px",
    sortable: true,
    searchable: true,
    filterable: true,
    filter_type: :select,
    filter_options: -> { Post.order(:title).limit(100).pluck(:title, :id).map { |title, id| [title.to_s.truncate(50), id] } },
    visible: true,
    formatter: ->(value, record) {
      post_title = record.post.title.to_s.truncate(45)
      "<span class='text-sm text-gray-700'>#{post_title}</span>".html_safe
    }

  column :author,
    label: "Commenter",
    type: :string,
    width: "180px",
    sortable: true,
    searchable: true,
    filterable: true,
    filter_type: :select,
    filter_options: -> { Author.order(:name).pluck(:name, :id).map { |name, id| [name, id] } },
    visible: true,
    formatter: ->(value, record) {
      return "-" unless record.author
      author_name = record.author.name
      if record.author.verified
        "#{author_name} <span class='text-xs text-green-600'>✓</span>".html_safe
      else
        author_name
      end
    }

  column :body,
    label: "Comment",
    type: :text,
    searchable: true,
    visible: true,
    formatter: ->(value, record) {
      truncated = value.to_s.truncate(80)
      "<span class='text-sm'>#{truncated}</span>".html_safe
    }

  column :status,
    label: "Status",
    type: :badge,
    sortable: true,
    filterable: true,
    filter_type: :select,
    filter_options: ["approved", "pending", "spam"],
    visible: true,
    formatter: {
      type: :badge,
      options: {
        colors: {
          "approved" => "green",
          "pending" => "yellow",
          "spam" => "red"
        }
      }
    }

  column :flagged,
    label: "Flagged",
    type: :boolean,
    sortable: true,
    filterable: true,
    filter_type: :select,
    filter_options: [["Yes", true], ["No", false]],
    visible: true,
    formatter: ->(value, record) {
      if value
        '<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-red-100 text-red-800">🚩 Flagged</span>'.html_safe
      else
        '<span class="text-gray-400 text-xs">-</span>'.html_safe
      end
    }

  column :likes_count,
    label: "Likes",
    type: :integer,
    sortable: true,
    visible: true,
    formatter: ->(value, record) {
      count = value || 0
      if count > 0
        "👍 #{count}"
      else
        "-"
      end
    }

  column :author_total_comments,
    label: "Author's Total",
    type: :integer,
    width: "140px",
    sortable: true,
    filterable: true,
    filter_type: :number,
    virtual: true,
    virtual_sql: "(SELECT COUNT(*) FROM comments AS c WHERE c.author_id = comments.author_id)",
    visible: true,
    formatter: ->(value, record) {
      count = value || 0
      color_class = if count > 50
        "bg-purple-100 text-purple-800 border-purple-200"
      elsif count > 20
        "bg-blue-100 text-blue-800 border-blue-200"
      elsif count > 5
        "bg-green-100 text-green-800 border-green-200"
      else
        "bg-gray-100 text-gray-600 border-gray-200"
      end
      "<span class='inline-flex items-center px-2 py-0.5 rounded text-xs font-medium border #{color_class}'>#{count} comments</span>".html_safe
    }

  column :created_at,
    label: "Posted",
    type: :datetime,
    sortable: true,
    filterable: true,
    filter_type: :date,
    visible: true,
    formatter: ->(value, record) {
      time_ago = ((Time.now - value) / 3600).round
      if time_ago < 1
        "Just now"
      elsif time_ago < 24
        "#{time_ago}h ago"
      elsif time_ago < 168
        "#{(time_ago / 24).round}d ago"
      else
        value.strftime("%b %d, %Y")
      end
    }

  column :actions,
    label: "Actions",
    type: :action,
    visible: true,
    formatter: :action,
    formatter_options: {
      actions: [
        {
          label: "View",
          url: ->(record) { "/comments/#{record.id}" },
          color: "primary"
        },
        {
          label: "Edit",
          url: ->(record) { "/comments/#{record.id}/edit" }
        },
        {
          label: "Delete",
          url: ->(record) { "/comments/#{record.id}" },
          method: :delete,
          color: "danger",
          confirm: "Are you sure?"
        }
      ]
    }

  per_page 30
  per_page_options [10, 30, 50, 100]
  default_sort :created_at, :desc
  table_min_height "450px"
  table_max_height "650px"

  row_link ->(record) { "/comments/#{record.id}" }
end