diff --git a/app/components/alchemy/admin/resource_table.rb b/app/components/alchemy/admin/resource_table.rb new file mode 100644 index 0000000000..daff2014f4 --- /dev/null +++ b/app/components/alchemy/admin/resource_table.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Alchemy + module Admin + class ResourceTable < ViewComponent::Base + include BaseHelper + + attr_reader :columns, :collection, :nothing_found_label + + erb_template <<~ERB + <% if collection.any? %> + + + + <% columns.each do |column| %> + + <% end %> + + + + <% collection.each do |row| %> + + <% columns.each do |column| %> + + <% end %> + + <% end %> + +
<%= column.label || column.name %>
+ <%= view_context.capture(row, &column.block) %> +
+ <% else %> +
+ <%= render_icon('info') %> + <%= nothing_found_label %> +
+ <% end %> + ERB + + def initialize(collection, nothing_found_label: Alchemy.t("Nothing found")) + @collection = collection + @nothing_found_label = nothing_found_label + @columns = [] + end + + def add_column(name, label: nil, sortable: true, &block) + @columns << Column.new(name, label: label, sortable: sortable, &block) + end + + private + + ## + # the before_render - method is necessary to force ViewComponent to evaluate the add_column - calls + def before_render + content + end + + class Column + attr_reader :block, :label, :name, :sortable + + def initialize(name, sortable:, label: nil, &block) + @name = name + @label = label + @sortable = sortable + @block = block || lambda { |item| item[name] } + end + end + end + end +end diff --git a/spec/components/alchemy/admin/resource_table_spec.rb b/spec/components/alchemy/admin/resource_table_spec.rb new file mode 100644 index 0000000000..eb460ecfc0 --- /dev/null +++ b/spec/components/alchemy/admin/resource_table_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Alchemy::Admin::ResourceTable, type: :component do + let(:collection) { [] } + before do + render + end + + subject(:render) do + render_inline(described_class.new(collection)) + end + + context "with data" do + let(:collection) { + [ + {name: "Foo", description: "Awesome description"}, + {name: "Bar", description: "Another description"} + ] + } + + it "doesn't renders an info message" do + expect(page).to_not have_content("Nothing found") + end + + context "columns without block" do + subject(:render) do + render_inline(described_class.new(collection)) do |component| + component.add_column(:name) + component.add_column(:description) + end + end + + it "renders a table header" do + expect(page).to have_selector("table th", text: "name") + expect(page).to have_selector("table th", text: "description") + end + + it "renders a table cell" do + expect(page).to have_selector("table td.name", text: "Foo") + expect(page).to have_selector("table td.description", text: "Awesome description") + end + end + + context "columns with custom label" do + subject(:render) do + render_inline(described_class.new(collection)) do |component| + component.add_column(:name, label: "Awesome Name") + end + end + + it "renders a table header with custom label" do + expect(page).to have_selector("table th", text: "Awesome Name") + end + end + + context "columns with a custom block" do + subject(:render) do + render_inline(described_class.new(collection)) do |component| + component.add_column(:description) { |item| item[:description].truncate(10) } + end + end + + it "renders a table cell with a custom block" do + expect(page).to have_selector("table td", text: "Awesome...") + end + end + end + + context "without any data" do + it "renders an info message" do + expect(page).to have_content("Nothing found") + end + + context "with another nothing found - label" do + subject(:render) do + render_inline(described_class.new(collection, nothing_found_label: "No user found")) + end + + it "renders an info message" do + expect(page).to have_content("No user found") + end + end + end +end