Posts

Infinite scroll — loads more content as you scroll. No pagination controls.

Posts Table

Export CSV

Loading data...

Filters

Table definition app/infotable/posts_table.rb
class PostsTable < Infotable::Base
  query do
    Post.includes(:author, :comments)
  end

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

  column :title,
    label: "Post Title",
    type: :string,
    width: "350px",
    sortable: true,
    searchable: true,
    filterable: true,
    filter_type: :text,
    visible: true,
    formatter: ->(value, record) {
      truncated = value.to_s.length > 50 ? "#{value[0..50]}..." : value
      "<strong class='text-gray-900'>#{truncated}</strong>".html_safe
    }

  column :author,
    label: "Author",
    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-green-600'>✓</span>".html_safe
      else
        author_name
      end
    }

  column :category,
    label: "Category",
    type: :badge,
    sortable: true,
    filterable: true,
    filter_type: :select,
    filter_options: -> { Post.distinct.pluck(:category).compact.sort },
    visible: true,
    formatter: {
      type: :badge,
      options: {
        colors: {
          "Technology" => "blue",
          "Design" => "purple",
          "Business" => "green",
          "Lifestyle" => "yellow",
          "Travel" => "indigo",
          "Food" => "pink",
          "Health" => "red",
          "Education" => "cyan"
        }
      }
    }

  column :status,
    label: "Status",
    type: :badge,
    width: "110px",
    sortable: true,
    filterable: true,
    filter_type: :select,
    filter_options: [ "published", "draft", "archived" ],
    visible: true,
    formatter: {
      type: :badge,
      options: {
        colors: {
          "published" => "green",
          "draft" => "yellow",
          "archived" => "gray"
        }
      }
    }

  column :featured,
    label: "Featured",
    type: :boolean,
    width: "90px",
    sortable: true,
    filterable: true,
    filter_type: :select,
    filter_options: [ [ "Yes", true ], [ "No", false ] ],
    visible: true,
    formatter: ->(value, record) {
      if value
        '<span class="text-yellow-500 text-lg">⭐</span>'.html_safe
      else
        '<span class="text-gray-300">☆</span>'.html_safe
      end
    }

  column :views_count,
    label: "Views",
    type: :integer,
    width: "100px",
    sortable: true,
    visible: true,
    formatter: ->(value, record) {
      if value.nil?
        "0"
      elsif value >= 1000000
        "#{(value / 1000000.0).round(1)}M"
      elsif value >= 1000
        "#{(value / 1000.0).round(1)}K"
      else
        value.to_s
      end
    }

  column :likes_count,
    label: "Likes",
    type: :integer,
    width: "80px",
    sortable: true,
    visible: true,
    formatter: ->(value, record) {
      count = value || 0
      "❤️ #{count}".html_safe
    }

  column :comments_count,
    label: "Comments",
    type: :integer,
    width: "100px",
    sortable: true,
    visible: true,
    formatter: ->(value, record) {
      count = record.comments.count
      "💬 #{count}"
    }

  column :reading_time,
    label: "Read Time",
    type: :integer,
    width: "100px",
    sortable: true,
    visible: true,
    formatter: ->(value, record) {
      value ? "#{value} min" : "-"
    }

  column :published_at,
    label: "Published",
    type: :datetime,
    sortable: true,
    filterable: true,
    filter_type: :date,
    visible: true,
    formatter: ->(value, record) {
      if value
        time_ago = ((Time.now - value) / 86400).round
        if time_ago == 0
          "Today"
        elsif time_ago == 1
          "Yesterday"
        elsif time_ago < 7
          "#{time_ago}d ago"
        elsif time_ago < 30
          "#{(time_ago / 7).round}w ago"
        else
          value.strftime("%b %d, %Y")
        end
      else
        "Not published"
      end
    }

  column :created_at,
    label: "Created",
    type: :datetime,
    sortable: true,
    visible: false,
    formatter: :date,
    formatter_options: { format: :short }

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

  per_page 50
  per_page_options [ 25, 50, 100, 200 ]
  default_sort :published_at, :desc
  infinite_scroll true
  table_min_height "500px"
  table_max_height "700px"
end