Class: Plutonium::UI::Kanban::Resource

Inherits:
Component::Base show all
Includes:
Phlex::Rails::Helpers::TurboFrameTag, Phlex::Rails::Helpers::TurboStreamFrom, ColorDot
Defined in:
lib/plutonium/ui/kanban/resource.rb

Overview

Board shell — renders one lazy turbo-frame per column.

Each frame id is "kanban-col-". Its src merges view=kanban and column= into the current request URL so Task 6's column endpoint fills the body on demand. The frame already contains the column header (color dot + label) so the shell is meaningful while the body loads. The header deliberately omits a card-count badge: the shell has no card data, so a "0" would flash then vanish when Kanban::Column (which renders no count badge) replaces the frame body.

The outer wrapper carries data-controller="kanban" for the Stimulus drag controller wired in Task 11.

Realtime subscription: when board.realtime? is true, render_realtime_subscription is called before the board wrapper. Task 14 implements the broadcaster and fills in that method body.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ColorDot

#color_css_value, #render_color_dot

Methods included from Component::Behaviour

#around_template

Methods included from Component::Tokens

#classes, #tokens

Methods included from Component::Kit

#BuildActionButton, #BuildActionsDropdown, #BuildAvatar, #BuildBlock, #BuildBreadcrumbs, #BuildBulkActionsToolbar, #BuildColorModeSelector, #BuildDynaFrameContent, #BuildDynaFrameHost, #BuildEmptyCard, #BuildFrameNavigatorPanel, #BuildModalCentered, #BuildModalSlideover, #BuildPageHeader, #BuildPanel, #BuildRowActionsDropdown, #BuildSkeletonTable, #BuildTabList, #BuildTableFilterPills, #BuildTableInfo, #BuildTablePagination, #BuildTableScopesBar, #BuildTableScopesPills, #BuildTableSearchBar, #BuildTableToolbar, #BuildTableViewSwitcher, #method_missing, #respond_to_missing?

Constructor Details

#initialize(board:, grouped_data:, resource_definition:, resource_fields:, resource_class: nil, scoped_entity: nil) ⇒ Resource

Returns a new instance of Resource.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/plutonium/ui/kanban/resource.rb', line 52

def initialize(board:, grouped_data:, resource_definition:, resource_fields:,
  resource_class: nil, scoped_entity: nil)
  @board = board
  @grouped_data = grouped_data
  @resource_definition = resource_definition
  # TODO (Tasks 6/10): resource_fields arrives ALREADY resolved — the
  # column endpoint / index page is responsible for the
  # `board.card_fields || definition.grid_fields` fallback before
  # constructing this component. Kanban::Resource (and the Card it
  # eventually builds) just receives the final field list and renders
  # it; it does not resolve card_fields itself.
  @resource_fields = resource_fields
  # Used by render_realtime_subscription (Task 14) to scope the
  # ActionCable stream to the correct tenant + resource.
  @resource_class = resource_class
  @scoped_entity = scoped_entity
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Plutonium::UI::Component::Kit

Instance Attribute Details

#boardObject (readonly)

Returns the value of attribute board.



49
50
51
# File 'lib/plutonium/ui/kanban/resource.rb', line 49

def board
  @board
end

#grouped_dataObject (readonly)

Returns the value of attribute grouped_data.



49
50
51
# File 'lib/plutonium/ui/kanban/resource.rb', line 49

def grouped_data
  @grouped_data
end

#resource_classObject (readonly)

Returns the value of attribute resource_class.



49
50
51
# File 'lib/plutonium/ui/kanban/resource.rb', line 49

def resource_class
  @resource_class
end

#resource_definitionObject (readonly)

Returns the value of attribute resource_definition.



49
50
51
# File 'lib/plutonium/ui/kanban/resource.rb', line 49

def resource_definition
  @resource_definition
end

#resource_fieldsObject (readonly)

Returns the value of attribute resource_fields.



49
50
51
# File 'lib/plutonium/ui/kanban/resource.rb', line 49

def resource_fields
  @resource_fields
end

#scoped_entityObject (readonly)

Returns the value of attribute scoped_entity.



49
50
51
# File 'lib/plutonium/ui/kanban/resource.rb', line 49

def scoped_entity
  @scoped_entity
end

Class Method Details

Per-board collapse cookie. Stores ONLY the column keys whose collapse state differs from the server default (a compact delta): a board sitting at its defaults writes nothing, and toggling a column back to default drops its key — so the cookie can't balloon. Path-scoped to the engine mount so it rides only this board's requests. The server reads it to render each column in the user's state (no client re-apply / FOUC); the kanban controller writes it on toggle.



34
35
36
# File 'lib/plutonium/ui/kanban/resource.rb', line 34

def self.collapse_cookie_name(resource_class)
  "pu_kbc_#{resource_class.name.gsub("::", "_").underscore}"
end


38
39
40
41
# File 'lib/plutonium/ui/kanban/resource.rb', line 38

def self.collapse_cookie_path(request)
  path = request.script_name.to_s
  path.empty? ? "/" : path
end

.collapse_flips(cookie_value) ⇒ Object

The set of column keys (strings) flipped from their default, parsed from the cookie value. Order/whitespace-tolerant; blanks dropped.



45
46
47
# File 'lib/plutonium/ui/kanban/resource.rb', line 45

def self.collapse_flips(cookie_value)
  cookie_value.to_s.split(",").filter_map { |k| k.strip.presence }
end

Instance Method Details

#view_templateObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/plutonium/ui/kanban/resource.rb', line 70

def view_template
  render_realtime_subscription if board.realtime?

  # Wrap in the filter-panel controller so the toolbar's Filter button
  # can open the slideover — same structure as Grid/Table::Resource, so
  # the board carries the shared view switcher (Table | Grid | Board),
  # search, scopes, and filters rather than rendering bare columns.
  div(data: filter_panel_controller_data) do
    # Persistent append target for move-rejection toasts. It lives
    # OUTSIDE the per-column turbo-frames so it survives the frame swaps
    # a move triggers; toasts are position:fixed, so its location in the
    # DOM has no layout effect. The move handler appends a flash toast
    # here via turbo_stream when a drop is rejected (WIP / accepts /
    # locked) so the snap-back is explained rather than silent.
    div(id: "kanban-flash")
    render_scopes_pills
    render_toolbar

    div(
      # `data-turbo-permanent` freezes the loaded board across index
      # navigations (search / filter / scope all change the URL): Turbo
      # transplants THIS element instead of re-rendering it as empty lazy
      # shells, so the columns never blank. The `kanban` controller then
      # syncs the frozen frames to the new URL (morphing cards in place).
      # The id MUST be resource-scoped — a shared id would make Turbo
      # preserve one resource's board when navigating to another's.
      id: "kanban-board-#{resource_class.model_name.param_key}",
      class: "pu-kanban-board flex gap-4 overflow-x-auto p-4 min-h-0",
      data: {
        turbo_permanent: true,
        controller: "kanban",
        # Stimulus value consumed by the drag controller to build the
        # per-record move URL at drop time. The collection path comes from
        # request.path so tenant / engine scoping is preserved automatically.
        # Example: /admin/tasks/__ID__/kanban_move
        kanban_move_url_template_value: kanban_move_url_template,
        # Name + path the controller uses to write the collapse cookie —
        # must match what the server reads (see collapse_cookie_name/path).
        kanban_collapse_cookie_value: self.class.collapse_cookie_name(resource_class),
        kanban_collapse_path_value: self.class.collapse_cookie_path(request)
      }
    ) do
      grouped_data.each do |entry|
        render_column_frame(entry[:column], collapsed: entry[:collapsed])
      end
    end

    render_filter_slideover if current_query_object.filter_definitions.present?
  end
end