Merge pull request #899 from basecamp/three-col-collections
Add a third, "On deck" column in Collections
This commit is contained in:
@@ -185,8 +185,10 @@
|
||||
--btn-border-radius: 0;
|
||||
--radius: 0.3em;
|
||||
|
||||
flex: 1 0 33%;
|
||||
inline-size: 100%;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
form {
|
||||
@@ -197,7 +199,6 @@
|
||||
border-end-start-radius: var(--radius);
|
||||
border-inline-end: 0;
|
||||
border-start-start-radius: var(--radius);
|
||||
flex: 1 0 50%;
|
||||
padding-inline-end: 0.8em;
|
||||
}
|
||||
|
||||
@@ -205,7 +206,6 @@
|
||||
border-end-end-radius: var(--radius);
|
||||
border-inline-start: 0;
|
||||
border-start-end-radius: var(--radius);
|
||||
flex: 1 0 50%;
|
||||
padding-inline-start: 0.8em;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#main:has(.card-columns) {
|
||||
--cards-gap: min(2cqi, 1rem);
|
||||
--gradient-border: linear-gradient(to right,var(--color-canvas), var(--color-ink-lighter) 3%, var(--color-ink-lighter) 97%, var(--color-canvas));
|
||||
--reserved-bubble-space: calc(var(--bubble-size) + var(--bubble-gap));
|
||||
--reserved-bubble-space: calc(var(--bubble-size) / 2 + var(--bubble-gap));
|
||||
--bubble-gap: 0.5rem;
|
||||
--bubble-size: 4rem;
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
@media (max-width: 639px) {
|
||||
--bubble-gap: -0.5rem;
|
||||
--bubble-size: 3rem;
|
||||
--reserved-bubble-space: calc(var(--bubble-size) / 2 + var(--bubble-gap));
|
||||
}
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
margin: auto;
|
||||
max-inline-size: 100%;
|
||||
padding-inline: var(--reserved-bubble-space);
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
@media (min-width: 640px) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
grid-template-columns: repeat(3, 33%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
@media (max-width: 639px) {
|
||||
padding-block-end: calc(var(--cards-gap) * 2);
|
||||
padding-inline-start: var(--reserved-bubble-space);
|
||||
padding-inline-start: var(--cards-gap);
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
.cards--considering {
|
||||
@media (min-width: 640px) {
|
||||
padding-inline: var(--reserved-bubble-space) var(--cards-gap);
|
||||
padding-inline: var(--cards-gap);
|
||||
|
||||
&:after {
|
||||
background: linear-gradient(var(--color-canvas), var(--color-ink-lighter) 1%, var(--color-ink-lighter) 99%, var(--color-canvas));
|
||||
@@ -222,12 +222,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* On Deck
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.cards--on-deck {
|
||||
@media (min-width: 640px) {
|
||||
padding-inline: var(--cards-gap);
|
||||
|
||||
&:after {
|
||||
background: linear-gradient(var(--color-canvas), var(--color-ink-lighter) 1%, var(--color-ink-lighter) 99%, var(--color-canvas));
|
||||
block-size: calc(100% + 1.2em);
|
||||
content: "";
|
||||
display: block;
|
||||
inline-size: 1px;
|
||||
inset: -0.6em 0 -0.6em 100%;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
&::before {
|
||||
background: var(--gradient-border);
|
||||
block-size: 1px;
|
||||
content: "";
|
||||
inset: 0 0 auto;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Doing
|
||||
/* ------------------------------------------------------------------------ */
|
||||
|
||||
.cards--doing {
|
||||
@media (min-width: 640px) {
|
||||
padding-inline: var(--cards-gap) var(--reserved-bubble-space);
|
||||
padding-inline: var(--cards-gap);
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
|
||||
@@ -8,7 +8,7 @@ class Cards::DropsController < ApplicationController
|
||||
end
|
||||
|
||||
private
|
||||
VALID_DROP_TARGETS = %w[ considering doing ]
|
||||
VALID_DROP_TARGETS = %w[ considering on_deck doing ]
|
||||
|
||||
def set_card
|
||||
@card = Current.user.accessible_cards.find(params[:dropped_item_id])
|
||||
@@ -26,6 +26,8 @@ class Cards::DropsController < ApplicationController
|
||||
case @drop_target
|
||||
when :considering
|
||||
@card.reconsider
|
||||
when :on_deck
|
||||
@card.move_to_on_deck
|
||||
when :doing
|
||||
@card.engage
|
||||
end
|
||||
@@ -33,6 +35,6 @@ class Cards::DropsController < ApplicationController
|
||||
|
||||
def render_column_replacement
|
||||
page_and_filter = page_and_filter_for @filter.with(engagement_status: @drop_target.to_s), per_page: CardsController::PAGE_SIZE
|
||||
render turbo_stream: turbo_stream.replace("#{@drop_target}-cards", method: :morph, partial: "cards/index/engagement/#{@drop_target}", locals: page_and_filter.to_h)
|
||||
render turbo_stream: turbo_stream.replace("#{@drop_target.to_s.gsub('_', '-')}-cards", method: :morph, partial: "cards/index/engagement/#{@drop_target}", locals: page_and_filter.to_h)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,12 @@ class Cards::EngagementsController < ApplicationController
|
||||
include CardScoped
|
||||
|
||||
def create
|
||||
@card.engage
|
||||
case params[:engagement]
|
||||
when "doing"
|
||||
@card.engage
|
||||
when "on_deck"
|
||||
@card.move_to_on_deck
|
||||
end
|
||||
render_card_replacement
|
||||
end
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ class CardsController < ApplicationController
|
||||
|
||||
def index
|
||||
@considering = page_and_filter_for @filter.with(engagement_status: "considering"), per_page: PAGE_SIZE
|
||||
@on_deck = page_and_filter_for @filter.with(engagement_status: "on_deck"), per_page: PAGE_SIZE
|
||||
@doing = page_and_filter_for @filter.with(engagement_status: "doing"), per_page: PAGE_SIZE
|
||||
@closed = page_and_filter_for_closed_cards
|
||||
end
|
||||
|
||||
@@ -5,18 +5,24 @@ module Card::Engageable
|
||||
has_one :engagement, dependent: :destroy, class_name: "Card::Engagement"
|
||||
|
||||
scope :considering, -> { published_or_drafted_by(Current.user).open.where.missing(:engagement) }
|
||||
scope :doing, -> { published.open.joins(:engagement) }
|
||||
scope :on_deck, -> { published.open.joins(:engagement).where(card_engagements: { status: "on_deck" }) }
|
||||
scope :doing, -> { published.open.joins(:engagement).where(card_engagements: { status: "doing" }) }
|
||||
|
||||
scope :by_engagement_status, ->(status) do
|
||||
case status.to_s
|
||||
when "considering" then considering.with_golden_first
|
||||
when "on_deck" then on_deck.with_golden_first
|
||||
when "doing" then doing.with_golden_first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def doing?
|
||||
open? && published? && engagement.present?
|
||||
open? && published? && engagement&.status == "doing"
|
||||
end
|
||||
|
||||
def on_deck?
|
||||
open? && published? && engagement&.status == "on_deck"
|
||||
end
|
||||
|
||||
def considering?
|
||||
@@ -26,6 +32,8 @@ module Card::Engageable
|
||||
def engagement_status
|
||||
if doing?
|
||||
"doing"
|
||||
elsif on_deck?
|
||||
"on_deck"
|
||||
elsif considering?
|
||||
"considering"
|
||||
end
|
||||
@@ -35,7 +43,20 @@ module Card::Engageable
|
||||
unless doing?
|
||||
transaction do
|
||||
reopen
|
||||
create_engagement!
|
||||
create_engagement!(status: "doing")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def move_to_on_deck
|
||||
unless on_deck?
|
||||
transaction do
|
||||
reopen
|
||||
if engagement.present?
|
||||
engagement.update!(status: "on_deck")
|
||||
else
|
||||
create_engagement!(status: "on_deck")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
class Card::Engagement < ApplicationRecord
|
||||
belongs_to :card, class_name: "::Card", touch: true
|
||||
|
||||
validates :status, presence: true, inclusion: { in: %w[doing on_deck] }
|
||||
end
|
||||
|
||||
@@ -2,14 +2,23 @@
|
||||
<div class="card-perma__notch card-perma__notch--top" id="<%= dom_id(@card, :card_engagement_toggle) %>" style="--card-color: <%= @card.color %>;">
|
||||
<div class="btn__group flex full-width txt-uppercase">
|
||||
<%= form_with url: card_engagement_path(@card), method: :delete, data: { controller: "form" } do |form| %>
|
||||
<%= form.label :engagement, value: "considering", class: "btn", aria: { label: "Move to Doing"} do %>
|
||||
<%= form.label :engagement, value: "considering", class: "btn", aria: { label: "Move to Considering"} do %>
|
||||
<%= form.radio_button :engagement, "considering", checked: @card.considering?, class: "for-screen-reader", data: { action: "change->form#submit" } %>
|
||||
<span>Considering</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= form_with url: card_engagement_path(@card), method: :post, data: { controller: "form" } do |form| %>
|
||||
<%= form.label :engagement, value: "doing", class: "btn", aria: { label: "Move back to Considering"} do %>
|
||||
<%= form.hidden_field :engagement, value: "on_deck" %>
|
||||
<%= form.label :engagement, value: "on_deck", class: "btn", aria: { label: "Move to On Deck"} do %>
|
||||
<%= form.radio_button :engagement, "on_deck", checked: @card.on_deck?, class: "for-screen-reader", data: { action: "change->form#submit" } %>
|
||||
<span>On Deck</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= form_with url: card_engagement_path(@card), method: :post, data: { controller: "form" } do |form| %>
|
||||
<%= form.hidden_field :engagement, value: "doing" %>
|
||||
<%= form.label :engagement, value: "doing", class: "btn", aria: { label: "Move to Doing"} do %>
|
||||
<%= form.radio_button :engagement, "doing", checked: @card.doing?, class: "for-screen-reader", data: { action: "change->form#submit" } %>
|
||||
<span>Doing</span>
|
||||
<% end %>
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
dragend->drag-and-drop#dragEnd" } do %>
|
||||
<div class="card-columns">
|
||||
<%= render "cards/index/engagement/considering", **@considering.to_h %>
|
||||
<%= render "cards/index/engagement/on_deck", **@on_deck.to_h %>
|
||||
<%= render "cards/index/engagement/doing", **@doing.to_h %>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="cards__heading" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
|
||||
<button class="cards__filter btn input input--select borderless overflow-ellipsis" data-action="click->dialog#open:stop">
|
||||
<% if filter.stages.any? %>
|
||||
<%= filter.stages.map(&:name).to_sentence %>
|
||||
Doing: <%= filter.stages.map(&:name).to_sentence %>
|
||||
<% else %>
|
||||
Doing
|
||||
<% end %>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<section id="on-deck-cards" class="cards cards--on-deck" data-drag-and-drop-target="container" data-drop-target="on_deck">
|
||||
<h2 class="cards__heading">
|
||||
Decided to do
|
||||
</h2>
|
||||
|
||||
<% if page.used? %>
|
||||
<%= render partial: "cards/display/preview", collection: page.records, as: :card, locals: { draggable: true }, cached: true %>
|
||||
|
||||
<% unless page.last? %>
|
||||
<%= cards_next_page_link "on-deck-cards", page: page, filter: filter %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="txt-medium translucent">Nothing here</p>
|
||||
<% end %>
|
||||
</section>
|
||||
@@ -0,0 +1,6 @@
|
||||
class AddStatusToCardEngagements < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_column :card_engagements, :status, :string, default: "doing", null: false
|
||||
add_index :card_engagements, :status
|
||||
end
|
||||
end
|
||||
Generated
+3
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_08_06_174718) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_08_12_195130) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.datetime "accessed_at"
|
||||
t.integer "collection_id", null: false
|
||||
@@ -107,8 +107,10 @@ ActiveRecord::Schema[8.1].define(version: 2025_08_06_174718) do
|
||||
create_table "card_engagements", force: :cascade do |t|
|
||||
t.integer "card_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.string "status", default: "doing", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["card_id"], name: "index_card_engagements_on_card_id"
|
||||
t.index ["status"], name: "index_card_engagements_on_status"
|
||||
end
|
||||
|
||||
create_table "card_goldnesses", force: :cascade do |t|
|
||||
|
||||
+27
-1
@@ -382,6 +382,16 @@ columns:
|
||||
comment:
|
||||
- *5
|
||||
- *6
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
|
||||
auto_increment:
|
||||
name: status
|
||||
cast_type: *7
|
||||
sql_type_metadata: *8
|
||||
'null': false
|
||||
default: doing
|
||||
default_function:
|
||||
collation:
|
||||
comment:
|
||||
- *9
|
||||
card_goldnesses:
|
||||
- *22
|
||||
@@ -1587,6 +1597,22 @@ indexes:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: card_engagements
|
||||
name: index_card_engagements_on_status
|
||||
unique: false
|
||||
columns:
|
||||
- status
|
||||
lengths: {}
|
||||
orders: {}
|
||||
opclasses: {}
|
||||
where:
|
||||
type:
|
||||
using:
|
||||
include:
|
||||
nulls_not_distinct:
|
||||
comment:
|
||||
valid: true
|
||||
card_goldnesses:
|
||||
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
|
||||
table: card_goldnesses
|
||||
@@ -2703,4 +2729,4 @@ indexes:
|
||||
comment:
|
||||
valid: true
|
||||
workflows: []
|
||||
version: 20250806174718
|
||||
version: 20250812195130
|
||||
|
||||
@@ -9,7 +9,16 @@ class Cards::EngagementsControllerTest < ActionDispatch::IntegrationTest
|
||||
card = cards(:text)
|
||||
|
||||
assert_changes -> { card.reload.doing? }, from: false, to: true do
|
||||
post card_engagement_path(card)
|
||||
post card_engagement_path(card), params: { engagement: "doing" }
|
||||
assert_card_container_rerendered(card)
|
||||
end
|
||||
end
|
||||
|
||||
test "create on_deck" do
|
||||
card = cards(:text)
|
||||
|
||||
assert_changes -> { card.reload.on_deck? }, from: false, to: true do
|
||||
post card_engagement_path(card), params: { engagement: "on_deck" }
|
||||
assert_card_container_rerendered(card)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,9 @@ class Card::EngageableTest < ActiveSupport::TestCase
|
||||
assert_not cards(:logo).considering?
|
||||
assert cards(:text).considering?
|
||||
|
||||
assert_not cards(:logo).on_deck?
|
||||
assert_not cards(:text).on_deck?
|
||||
|
||||
assert_equal "doing", cards(:logo).engagement_status
|
||||
assert_equal "considering", cards(:text).engagement_status
|
||||
end
|
||||
@@ -31,6 +34,7 @@ class Card::EngageableTest < ActiveSupport::TestCase
|
||||
|
||||
assert_not cards(:text).considering?
|
||||
assert_not cards(:text).doing?
|
||||
assert_not cards(:text).on_deck?
|
||||
|
||||
cards(:text).engage
|
||||
assert_not cards(:text).reload.closed?
|
||||
@@ -48,5 +52,9 @@ class Card::EngageableTest < ActiveSupport::TestCase
|
||||
|
||||
assert_includes Card.considering, cards(:text)
|
||||
assert_not_includes Card.considering, cards(:logo)
|
||||
|
||||
cards(:text).move_to_on_deck
|
||||
assert_includes Card.on_deck, cards(:text)
|
||||
assert_not_includes Card.on_deck, cards(:logo)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user