Paginate collection cards

This commit is contained in:
Jorge Manrubia
2025-04-10 15:08:18 +02:00
parent 411e223dc3
commit 695247ae9b
19 changed files with 149 additions and 49 deletions
@@ -0,0 +1,8 @@
class Cards::PreviewsController < ApplicationController
include FilterScoped
before_action :set_filter, only: :index
def index
set_page_and_extract_portion_from @filter.cards, per_page: CardsController::PAGE_SIZE
end
end
+13 -13
View File
@@ -1,17 +1,17 @@
require "ostruct"
class CardsController < ApplicationController
include CollectionScoped
include CollectionScoped, FilterScoped
skip_before_action :set_collection, only: :index
before_action :set_filter, only: :index
before_action :set_card, only: %i[ show edit update destroy ]
RECENTLY_CLOSED_LIMIT = 100
PAGE_SIZE = 50
def index
@considering_cards = @filter.cards.considering.load_async
@doing_cards = @filter.cards.doing.load_async
@closed_cards = @filter.with(indexed_by: "closed").cards.recently_closed_first.limit(RECENTLY_CLOSED_LIMIT).load_async
@considering = page_and_filter_for @filter.with(engagement_status: "considering"), per_page: PAGE_SIZE
@doing = page_and_filter_for @filter.with(engagement_status: "doing"), per_page: PAGE_SIZE
@closed = page_and_filter_for @filter.with(indexed_by: "closed"), per_page: PAGE_SIZE
end
def create
@@ -35,16 +35,16 @@ class CardsController < ApplicationController
end
private
DEFAULT_PARAMS = { indexed_by: "newest" }
def set_filter
@filter = Current.user.filters.from_params params.reverse_merge(**DEFAULT_PARAMS).permit(*Filter::PERMITTED_PARAMS)
end
def set_card
@card = @collection.cards.find params[:id]
end
def page_and_filter_for(filter, per_page: nil)
OpenStruct.new \
page: GearedPagination::Recordset.new(filter.cards, per_page:).page(1),
filter: filter
end
def card_params
params.expect(card: [ :status, :title, :color, :due_on, :image, :draft_comment, tag_ids: [] ])
end
+14
View File
@@ -0,0 +1,14 @@
module FilterScoped
extend ActiveSupport::Concern
included do
before_action :set_filter, only: :index
end
private
DEFAULT_PARAMS = { indexed_by: "newest" }
def set_filter
@filter = Current.user.filters.from_params params.reverse_merge(**DEFAULT_PARAMS).permit(*Filter::PERMITTED_PARAMS)
end
end
+16
View File
@@ -10,4 +10,20 @@ module CardsHelper
"--card-rotate: #{value}deg;"
end
def cards_next_page_link(target, page:, filter:, fetch_on_visible: false, data: {}, **options)
url = cards_previews_path(target: target, page: page.next_param, **filter.as_params)
if fetch_on_visible
data[:controller] = "#{data[:controller]} fetch-on-visible"
data[:fetch_on_visible_url_value] = url
end
link_to "Load more",
url,
id: "#{target}-load-page-#{page.next_param}",
data: { turbo_stream: true, **data },
class: "btn txt-small",
**options
end
end
+7
View File
@@ -29,6 +29,13 @@ class Card < ApplicationRecord
end
end
scope :by_engagement_status, ->(status) do
case status.to_s
when "considering" then considering
when "doing" then doing
end
end
def cache_key
[ super, collection&.name ].compact.join("/")
end
-21
View File
@@ -1,21 +0,0 @@
module Filter::Params
extend ActiveSupport::Concern
PERMITTED_PARAMS = %i[
indexed_by assignment_status collection_ids creator_ids
assignee_ids stage_ids tag_ids terms
].freeze
def as_params
self.class.normalize_params(
indexed_by: indexed_by,
assignment_status: assignment_status,
collection_ids: collections.ids,
creator_ids: creators.ids,
assignee_ids: assignees.ids,
stage_ids: stages.ids,
tag_ids: tags.ids,
terms: terms
)
end
end
+1
View File
@@ -20,6 +20,7 @@ class Filter < ApplicationRecord
@cards ||= begin
result = creator.accessible_cards.indexed_by(indexed_by)
result = result.active unless indexed_by.closed?
result = result.by_engagement_status(engagement_status) if engagement_status.present?
result = result.unassigned if assignment_status.unassigned?
result = result.assigned_to(assignees.ids) if assignees.present?
result = result.where(creator_id: creators.ids) if creators.present?
+5 -1
View File
@@ -16,7 +16,7 @@ module Filter::Fields
end
included do
store_accessor :fields, :assignment_status, :indexed_by, :terms
store_accessor :fields, :assignment_status, :indexed_by, :terms, :engagement_status
def assignment_status
super.to_s.inquiry
@@ -26,6 +26,10 @@ module Filter::Fields
(super || default_indexed_by).inquiry
end
def engagement_status
super&.inquiry
end
def terms
Array(super)
end
+2
View File
@@ -4,6 +4,7 @@ module Filter::Params
PERMITTED_PARAMS = [
:assignment_status,
:indexed_by,
:engagement_status,
assignee_ids: [],
creator_ids: [],
collection_ids: [],
@@ -35,6 +36,7 @@ module Filter::Params
def as_params
{}.tap do |params|
params[:indexed_by] = indexed_by
params[:engagement_status] = engagement_status
params[:assignment_status] = assignment_status
params[:terms] = terms
params[:tag_ids] = tags.ids
+3 -3
View File
@@ -7,8 +7,8 @@
<% end %>
<div class="cards--columns">
<%= render "cards/index/engagement/considering", cards: @considering_cards %>
<%= render "cards/index/engagement/doing", cards: @doing_cards, filter: @filter %>
<%= render "cards/index/engagement/considering", **@considering.to_h %>
<%= render "cards/index/engagement/doing", **@doing.to_h %>
</div>
<%= render "cards/index/engagement/closed", cards: @closed_cards, filter: @filter %>
<%= render "cards/index/engagement/closed", **@closed.to_h %>
@@ -3,9 +3,13 @@
Recently closed
</h2>
<section class="cards--grid">
<% if cards.any? %>
<%= render partial: "cards/display/preview", collection: cards, as: :card, cached: true %>
<section id="closed-cards" class="cards--grid">
<% if page.used? %>
<%= render partial: "cards/display/preview", collection: page.records, as: :card, cached: true %>
<% unless page.last? %>
<%= cards_next_page_link "closed-cards", page: page, filter: filter, fetch_on_visible: true %>
<% end %>
<% else %>
<p class="txt-medium translucent">Nothing here</p>
<% end %>
@@ -1,10 +1,14 @@
<section class="cards" style="align-items: center; border-inline-end: 1px solid var(--color-subtle);">
<section id="considering-cards" class="cards" style="align-items: center; border-inline-end: 1px solid var(--color-subtle);">
<h2 class="txt-align-center flex-inline center txt-uppercase txt-medium margin-none-block-start margin-block-end">
Considering
</h2>
<% if cards.any? %>
<%= render partial: "cards/display/mini", collection: cards, as: :card, cached: true %>
<% if page.used? %>
<%= render partial: "cards/display/mini", collection: page.records, as: :card, cached: true %>
<% unless page.last? %>
<%= cards_next_page_link "considering-cards", page: page, filter: filter %>
<% end %>
<% else %>
<p class="txt-medium translucent">Nothing here</p>
<% end %>
@@ -1,4 +1,4 @@
<section class="cards gap position-relative" style="align-items: center; view-transition-name: cards-container;" data-controller="card-size" data-action="turbo:morph@window->card-size#resize">
<section id="doing-cards" class="cards gap position-relative" style="align-items: center; view-transition-name: cards-container;" data-controller="card-size" data-action="turbo:morph@window->card-size#resize">
<% if workflow = filter.single_workflow %>
<%= render "cards/index/workflow_filter", workflow: workflow, filter: filter %>
<% else %>
@@ -7,8 +7,12 @@
</h2>
<% end %>
<% if cards.any? %>
<%= render partial: "cards/display/preview", collection: cards, as: :card, cached: true %>
<% if page.used? %>
<%= render partial: "cards/display/preview", collection: page.records, as: :card, cached: true %>
<% unless page.last? %>
<%= cards_next_page_link "doing-cards", page: page, filter: filter %>
<% end %>
<% else %>
<p class="txt-medium translucent">Nothing here</p>
<% end %>
@@ -0,0 +1,10 @@
<%= turbo_stream.remove "#{params[:target]}-load-page-#{@page.number}" %>
<%= turbo_stream.append params[:target] do %>
<%= render partial: @filter.engagement_status&.considering? ? "cards/display/mini" : "cards/display/preview",
collection: @page.records, as: :card, cached: true %>
<% unless @page.last? %>
<%= cards_next_page_link params[:target], page: @page, filter: @filter, fetch_on_visible: @filter.indexed_by.closed? %>
<% end %>
<% end %>
+4
View File
@@ -8,6 +8,10 @@ Rails.application.routes.draw do
end
end
namespace :cards do
resources :previews
end
resources :cards do
scope module: :cards do
resource :engagement
+30
View File
@@ -0,0 +1,30 @@
require_relative "../../config/environment"
CARDS_COUNT = 200
ApplicationRecord.current_tenant = "development-tenant"
account = Account.first
user = account.users.first
Current.session = user.sessions.last
collection = account.collections.first
# Doing
CARDS_COUNT.times do |index|
card = collection.cards.create!(title: "Doing card #{index}", creator: user, status: :published)
card.engage
end
# Considering
CARDS_COUNT.times do |index|
card = collection.cards.create!(title: "Considering card #{index}", creator: user, status: :published)
card.reconsider
end
# Completed
CARDS_COUNT.times do |index|
card = collection.cards.create!(title: "Popped card #{index}", creator: user, status: :published)
card.close
end
+1 -1
View File
@@ -5,7 +5,7 @@ require "active_support/core_ext/string" # for camelize
RENAME_RULES = {
"bubble" => "card",
"popped" => "closed",
"closed" => "closed",
"poppable" => "closeable",
"pop" => "closure",
"bucket" => "collection"
+1 -1
View File
@@ -9,7 +9,7 @@ EXCLUDED_DIRS = [ "db", ".git", "script/renaming" ].freeze
RENAMES = {
"bubble" => "card",
"poppable" => "closeable",
"popped" => "closed",
"closed" => "closed",
"pop" => "closure",
"bucket" => "collection"
}.freeze
@@ -0,0 +1,13 @@
require "test_helper"
class Cards::PreviewsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "index" do
get cards_previews_url(format: :turbo_stream)
assert_response :success
end
end