avo 2.13.5.pre.2 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -3
- data/README.md +7 -11
- data/app/components/avo/base_component.rb +3 -2
- data/app/components/avo/button_component.rb +1 -1
- data/app/components/avo/fields/common/progress_bar_component.html.erb +8 -0
- data/app/components/avo/fields/common/progress_bar_component.rb +15 -0
- data/app/components/avo/fields/common/single_file_viewer_component.html.erb +1 -1
- data/app/components/avo/fields/common/single_file_viewer_component.rb +12 -0
- data/app/components/avo/fields/progress_bar_field/index_component.html.erb +1 -6
- data/app/components/avo/fields/progress_bar_field/show_component.html.erb +1 -6
- data/app/components/avo/filters_component.html.erb +1 -1
- data/app/components/avo/filters_component.rb +11 -1
- data/app/components/avo/index/resource_table_component.html.erb +37 -2
- data/app/components/avo/index/resource_table_component.rb +15 -1
- data/app/components/avo/item_switcher_component.rb +1 -1
- data/app/components/avo/resource_component.rb +2 -3
- data/app/components/avo/tab_group_component.html.erb +4 -4
- data/app/components/avo/tab_group_component.rb +7 -1
- data/app/components/avo/tab_switcher_component.html.erb +28 -8
- data/app/components/avo/tab_switcher_component.rb +3 -1
- data/app/components/avo/views/resource_edit_component.rb +2 -0
- data/app/components/avo/views/resource_index_component.html.erb +3 -6
- data/app/components/avo/views/resource_index_component.rb +3 -2
- data/app/controllers/avo/actions_controller.rb +17 -4
- data/app/helpers/avo/resources_helper.rb +1 -1
- data/app/javascript/js/controllers/action_controller.js +11 -2
- data/app/javascript/js/controllers/fields/date_field_controller.js +5 -1
- data/app/javascript/js/controllers/item_select_all_controller.js +42 -4
- data/app/javascript/js/controllers/tabs_controller.js +13 -9
- data/app/views/avo/actions/show.html.erb +2 -1
- data/app/views/avo/base/index.html.erb +1 -0
- data/app/views/avo/partials/_table_header.html.erb +1 -1
- data/app/views/avo/partials/_view_toggle_button.html.erb +2 -2
- data/lib/avo/base_resource.rb +1 -5
- data/lib/avo/concerns/fetches_things.rb +2 -13
- data/lib/avo/concerns/has_fields.rb +2 -2
- data/lib/avo/configuration.rb +2 -2
- data/lib/avo/fields/base_field.rb +1 -1
- data/lib/avo/fields/concerns/is_required.rb +15 -1
- data/lib/avo/services/encryption_service.rb +33 -0
- data/lib/avo/tab_group.rb +3 -1
- data/lib/avo/tab_group_builder.rb +4 -4
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/initializer/avo.tt +9 -10
- data/lib/generators/avo/templates/locales/avo.en.yml +5 -1
- data/lib/generators/avo/templates/locales/avo.fr.yml +4 -0
- data/lib/generators/avo/templates/locales/avo.nb-NO.yml +5 -0
- data/lib/generators/avo/templates/locales/avo.pt-BR.yml +5 -0
- data/lib/generators/avo/templates/locales/avo.ro.yml +6 -0
- data/public/avo-assets/avo.css +44 -4
- data/public/avo-assets/avo.js +64 -64
- data/public/avo-assets/avo.js.map +2 -2
- metadata +7 -6
- data/app/controllers/avo/team_users_controller.rb +0 -4
- data/app/views/avo/partials/_tabs_toggle.html.erb +0 -20
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 58033ed16e041a89bae5bc13843079be1c9daa0f85004bf810645e24960e7526
         | 
| 4 | 
            +
              data.tar.gz: d401c18ba4edb14825be275b68851c0b9e32e3ff2427a79a27b5bafb377f4d51
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 32d0cda8da582e3b3104a0f676c968d11ed28cd222b26014651fec4405bdd14b868c39685f257d473c4d41c2eab5e0c1923924df097e82cc8ace744150f46bab
         | 
| 7 | 
            +
              data.tar.gz: 502abd413467a87d504ae3d82dffcee741fdc96a76a5458b8501826be5da494bf6e054af8a65d1a403a641e7475455a55acb1863e229ea6ad8a26037e255f731
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                avo (2. | 
| 4 | 
            +
                avo (2.14.0)
         | 
| 5 5 | 
             
                  active_link_to
         | 
| 6 6 | 
             
                  addressable
         | 
| 7 7 | 
             
                  breadcrumbs_on_rails
         | 
| @@ -256,8 +256,6 @@ GEM | |
| 256 256 | 
             
                nokogiri (1.13.7)
         | 
| 257 257 | 
             
                  mini_portile2 (~> 2.8.0)
         | 
| 258 258 | 
             
                  racc (~> 1.4)
         | 
| 259 | 
            -
                nokogiri (1.13.7-x86_64-linux)
         | 
| 260 | 
            -
                  racc (~> 1.4)
         | 
| 261 259 | 
             
                orm_adapter (0.5.0)
         | 
| 262 260 | 
             
                pagy (5.10.1)
         | 
| 263 261 | 
             
                  activesupport
         | 
    
        data/README.md
    CHANGED
    
    | @@ -7,12 +7,13 @@ | |
| 7 7 |  | 
| 8 8 | 
             
            
         | 
| 9 9 |  | 
| 10 | 
            -
            ** | 
| 10 | 
            +
            **Ruby on Rails application building framework.**
         | 
| 11 11 |  | 
| 12 12 | 
             
            Avo is a beautiful next-generation framework that empowers you, the developer, to create fantastic admin panels for your Ruby on Rails apps with the flexibility to fit your needs as you grow.
         | 
| 13 13 |  | 
| 14 14 | 
             
            ## Get started
         | 
| 15 15 |  | 
| 16 | 
            +
            ⚡️ **Install**: [docs.avohq.io/2.0/installation](https://docs.avohq.io/2.0/installation.html)\
         | 
| 16 17 | 
             
            ✨ **Website**: [avohq.io](https://avohq.io)\
         | 
| 17 18 | 
             
            📚 **Documentation**: [docs.avohq.io](https://docs.avohq.io)\
         | 
| 18 19 | 
             
            🗺 **Roadmap**: [GitHub Roadmap](https://github.com/orgs/avo-hq/projects/3)\
         | 
| @@ -43,24 +44,19 @@ Avo is a beautiful next-generation framework that empowers you, the developer, t | |
| 43 44 | 
             
              - **Localization** - Have it available in any language you need. [docs](https://docs.avohq.io/2.0/localization.html)
         | 
| 44 45 | 
             
              - **No asset pipeline pollution** - Bring your own asset pipeline. [docs](https://docs.avohq.io/2.0/custom-asset-pipeline.html)
         | 
| 45 46 | 
             
              - **Mobile interface** - Check your data on the go from any mobile device.
         | 
| 47 | 
            +
              - **Tabbed interface** - Conditionally show the data you need
         | 
| 48 | 
            +
              - **Menu builder** - Group and surface information as you need to
         | 
| 49 | 
            +
              - **Branding** - Make it look
         | 
| 46 50 |  | 
| 47 51 | 
             
            ## Some of the things we're going focusing on next
         | 
| 48 52 |  | 
| 49 | 
            -
            Theming ⭐️   notifications ⭐️   Resource segmentation ⭐️   filterable fields ⭐️   inline editing ⭐️   multilingual records ⭐️    | 
| 53 | 
            +
            Theming ⭐️   notifications ⭐️   Resource segmentation ⭐️   filterable fields ⭐️   inline editing ⭐️   multilingual records ⭐️   keyboard shortcuts ⭐️   track resource changes ⭐️   smart resource generation ⭐️   live resources ⭐️   columns view ⭐️   list view ⭐️   custom action items ⭐️   command bar ⭐️    use fields DSL in your custom views
         | 
| 50 54 |  | 
| 51 55 | 
             
            For more up-to-date info check out our 🗺 [Roadmap](https://github.com/orgs/avo-hq/projects/3).
         | 
| 52 56 |  | 
| 53 57 | 
             
            # Installation
         | 
| 54 | 
            -
            Add this line to your application's `Gemfile`:
         | 
| 55 58 |  | 
| 56 | 
            -
             | 
| 57 | 
            -
            gem 'avo'
         | 
| 58 | 
            -
            ```
         | 
| 59 | 
            -
             | 
| 60 | 
            -
            And then execute:
         | 
| 61 | 
            -
            ```bash
         | 
| 62 | 
            -
            $ bundle install
         | 
| 63 | 
            -
            ```
         | 
| 59 | 
            +
            Use this RailsBytes template to get started quick `rails app:template LOCATION='https://avohq.io/app-template'`. If you need a more detailed guide, follow [this page](https://docs.avohq.io/2.0/installation.html).
         | 
| 64 60 |  | 
| 65 61 | 
             
            # Contributing
         | 
| 66 62 |  | 
| @@ -9,9 +9,10 @@ class Avo::BaseComponent < ViewComponent::Base | |
| 9 9 |  | 
| 10 10 | 
             
              private
         | 
| 11 11 |  | 
| 12 | 
            -
              #  | 
| 12 | 
            +
              # Figure out what is the corresponding field for this @reflection
         | 
| 13 13 | 
             
              def field
         | 
| 14 | 
            -
                 | 
| 14 | 
            +
                fields = ::Avo::App.get_resource_by_model_name(@reflection.active_record.name).get_field_definitions
         | 
| 15 | 
            +
                fields.find { |f| f.id == @reflection.name }
         | 
| 15 16 | 
             
              rescue
         | 
| 16 17 | 
             
                nil
         | 
| 17 18 | 
             
              end
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            # A button/link can have the following settings:
         | 
| 4 | 
            -
            # style: primary/ | 
| 4 | 
            +
            # style: primary/outline/text
         | 
| 5 5 | 
             
            # size: :xs :sm, :md, :lg
         | 
| 6 6 | 
             
            class Avo::ButtonComponent < ViewComponent::Base
         | 
| 7 7 | 
             
              def initialize(path = nil, size: :md, style: :outline, color: :gray, icon: nil, icon_class: "", is_link: false, rounded: true, compact: false, **args)
         | 
| @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            <div class="flex items-center">
         | 
| 2 | 
            +
              <% if display_value %>
         | 
| 3 | 
            +
                <div class="text-right text-sm font-semibold leading-none mr-2 min-w-[2rem]">
         | 
| 4 | 
            +
                  <%= value %><%= value_suffix if value_suffix.present? %>
         | 
| 5 | 
            +
                </div>
         | 
| 6 | 
            +
              <% end %>
         | 
| 7 | 
            +
              <progress min="0" max="<%= max %>" value="<%= value %>" class="block w-full min-w-[6rem]"></progress>
         | 
| 8 | 
            +
            </div>
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Avo::Fields::Common::ProgressBarComponent < ViewComponent::Base
         | 
| 4 | 
            +
              attr_reader :value
         | 
| 5 | 
            +
              attr_reader :display_value
         | 
| 6 | 
            +
              attr_reader :value_suffix
         | 
| 7 | 
            +
              attr_reader :max
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def initialize(value:, display_value: false, value_suffix: nil, max: 100)
         | 
| 10 | 
            +
                @value = value
         | 
| 11 | 
            +
                @display_value = display_value
         | 
| 12 | 
            +
                @value_suffix = value_suffix
         | 
| 13 | 
            +
                @max = max
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -21,7 +21,7 @@ | |
| 21 21 | 
             
                <div class="flex space-x-2">
         | 
| 22 22 | 
             
                  <div class="flex">
         | 
| 23 23 | 
             
                    <% if @resource.authorization.authorize_action(:download_attachments?, raise_exception: false) %>
         | 
| 24 | 
            -
                      <%= a_link  | 
| 24 | 
            +
                      <%= a_link file.url(disposition: :attachment),
         | 
| 25 25 | 
             
                        icon: 'heroicons/outline/download',
         | 
| 26 26 | 
             
                        color: :primary,
         | 
| 27 27 | 
             
                        download: true,
         | 
| @@ -40,4 +40,16 @@ class Avo::Fields::Common::SingleFileViewerComponent < ViewComponent::Base | |
| 40 40 | 
             
              rescue
         | 
| 41 41 | 
             
                false
         | 
| 42 42 | 
             
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              def render?
         | 
| 45 | 
            +
                record_persisted?
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
               # If model is not persistent blob is automatically destroyed otherwise it can be "lost" on storage
         | 
| 49 | 
            +
              def record_persisted?
         | 
| 50 | 
            +
                return true if @resource.model.persisted?
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                ActiveStorage::Blob.destroy(file.blob_id) if file.blob_id.present?
         | 
| 53 | 
            +
                false
         | 
| 54 | 
            +
              end
         | 
| 43 55 | 
             
            end
         | 
| @@ -1,8 +1,3 @@ | |
| 1 1 | 
             
            <%= index_field_wrapper field: @field, resource: @resource, flush: true do %>
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                <div class="text-center text-sm font-semibold w-full leading-none mb-1">
         | 
| 4 | 
            -
                  <%= @field.value %><%= @field.value_suffix if @field.value_suffix.present? %>
         | 
| 5 | 
            -
                </div>
         | 
| 6 | 
            -
              <% end %>
         | 
| 7 | 
            -
              <progress min="0" max="<%= @field.max %>" value="<%= @field.value %>" class="block w-24"></progress>
         | 
| 2 | 
            +
              <%= render Avo::Fields::Common::ProgressBarComponent.new value: @field.value, display_value: @field.display_value, value_suffix: @field.value_suffix, max: @field.max %>
         | 
| 8 3 | 
             
            <% end %>
         | 
| @@ -1,8 +1,3 @@ | |
| 1 1 | 
             
            <%= show_field_wrapper field: @field, resource: @resource, index: @index do %>
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                <div class="text-center text-sm font-semibold w-full leading-none mb-1">
         | 
| 4 | 
            -
                  <%= @field.value %><%= @field.value_suffix if @field.value_suffix.present? %>
         | 
| 5 | 
            -
                </div>
         | 
| 6 | 
            -
              <% end %>
         | 
| 7 | 
            -
              <progress min="0" max="<%= @field.max %>" value="<%= @field.value %>" class="block w-full"></progress>
         | 
| 2 | 
            +
              <%= render Avo::Fields::Common::ProgressBarComponent.new value: @field.value, display_value: @field.display_value, value_suffix: @field.value_suffix, max: @field.max %>
         | 
| 8 3 | 
             
            <% end %>
         | 
| @@ -22,7 +22,7 @@ | |
| 22 22 | 
             
                  <% end %>
         | 
| 23 23 | 
             
                  <div class="p-4 border-gray-300 border-t">
         | 
| 24 24 | 
             
                    <% if params[:filters].present? %>
         | 
| 25 | 
            -
                      <%= a_link  | 
| 25 | 
            +
                      <%= a_link reset_path, data: {turbo_frame: params[:turbo_frame]}, color: :gray, size: :sm, class: 'w-full justify-center' do %>
         | 
| 26 26 | 
             
                        <%= t('avo.reset_filters') %>
         | 
| 27 27 | 
             
                      <% end %>
         | 
| 28 28 | 
             
                    <% else %>
         | 
| @@ -3,13 +3,23 @@ | |
| 3 3 | 
             
            class Avo::FiltersComponent < ViewComponent::Base
         | 
| 4 4 | 
             
              include Avo::ApplicationHelper
         | 
| 5 5 |  | 
| 6 | 
            -
              def initialize(filters: [], resource: nil, applied_filters: [])
         | 
| 6 | 
            +
              def initialize(filters: [], resource: nil, applied_filters: [], parent_model: nil)
         | 
| 7 7 | 
             
                @filters = filters
         | 
| 8 8 | 
             
                @resource = resource
         | 
| 9 9 | 
             
                @applied_filters = applied_filters
         | 
| 10 | 
            +
                @parent_model = parent_model
         | 
| 10 11 | 
             
              end
         | 
| 11 12 |  | 
| 12 13 | 
             
              def render?
         | 
| 13 14 | 
             
                @filters.present?
         | 
| 14 15 | 
             
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def reset_path
         | 
| 18 | 
            +
                # If come from a association page
         | 
| 19 | 
            +
                if @parent_model.present?
         | 
| 20 | 
            +
                  helpers.related_resources_path(@parent_model, @parent_model, filters: nil, keep_query_params: true)
         | 
| 21 | 
            +
                else
         | 
| 22 | 
            +
                  helpers.resources_path(resource: @resource, filters: nil, keep_query_params: true)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 15 25 | 
             
            end
         | 
| @@ -1,5 +1,40 @@ | |
| 1 | 
            -
            <div class="w-full " | 
| 2 | 
            -
               | 
| 1 | 
            +
            <div class="w-full relative"
         | 
| 2 | 
            +
              data-controller="item-select-all"
         | 
| 3 | 
            +
              data-resource-name="<%= @resource.model_key %>"
         | 
| 4 | 
            +
              data-item-select-all-selected-all-value="false"
         | 
| 5 | 
            +
              data-item-select-all-selected-all-query-value="<%= encrypted_query %>"
         | 
| 6 | 
            +
              data-selected-resources-name="<%= @resource.model_key %>"
         | 
| 7 | 
            +
              data-selected-resources="[]"
         | 
| 8 | 
            +
            >
         | 
| 9 | 
            +
              <% if pagy.pages > 1 %>
         | 
| 10 | 
            +
                <div class="absolute z-30 w-full ml-10 pt-px hidden" data-item-select-all-target="selectAllOvelay">
         | 
| 11 | 
            +
                  <div class="bg-white flex items-center h-9 mt-0.5">
         | 
| 12 | 
            +
                    <div class="mt-1.5" data-item-select-all-target="unselectedMessage">
         | 
| 13 | 
            +
                      <%= t "avo.x_records_selected_from_a_total_of_x_html", selected: pagy.in, count: pagy.count %>
         | 
| 14 | 
            +
                      <%= a_link request.url,
         | 
| 15 | 
            +
                        size: :xs,
         | 
| 16 | 
            +
                        color: :primary,
         | 
| 17 | 
            +
                        class: "relative -top-[0.1rem] ml-2 cursor-pointer text-primary-500",
         | 
| 18 | 
            +
                        data: {action: "click->item-select-all#selectAll"} do
         | 
| 19 | 
            +
                      %>
         | 
| 20 | 
            +
                        <%= t "avo.select_all_matching" %>
         | 
| 21 | 
            +
                      <% end %>
         | 
| 22 | 
            +
                    </div>
         | 
| 23 | 
            +
                    <div class="mt-1.5" data-item-select-all-target="selectedMessage" class="hidden">
         | 
| 24 | 
            +
                      <%= t "avo.x_records_selected_from_all_pages_html", count: pagy.count %>
         | 
| 25 | 
            +
                      <%= a_link request.url,
         | 
| 26 | 
            +
                        size: :xs,
         | 
| 27 | 
            +
                        color: :primary,
         | 
| 28 | 
            +
                        class: "relative -top-[0.1rem] ml-2 cursor-pointer text-primary-500",
         | 
| 29 | 
            +
                        data: {action: "click->item-select-all#selectAll"} do
         | 
| 30 | 
            +
                      %>
         | 
| 31 | 
            +
                        <%= t("avo.undo").humanize %>
         | 
| 32 | 
            +
                      <% end %>
         | 
| 33 | 
            +
                    </div>
         | 
| 34 | 
            +
                  </div>
         | 
| 35 | 
            +
                </div>
         | 
| 36 | 
            +
              <% end %>
         | 
| 37 | 
            +
              <table class="w-full px-4 bg-white">
         | 
| 3 38 | 
             
                <%= render partial: 'avo/partials/table_header', locals: {fields: @resource.get_fields(reflection: @reflection, only_root: true)} %>
         | 
| 4 39 | 
             
                <tbody class="divide-y">
         | 
| 5 40 | 
             
                  <% @resources.each_with_index do |resource, index| %>
         | 
| @@ -1,11 +1,25 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            class Avo::Index::ResourceTableComponent < ViewComponent::Base
         | 
| 4 | 
            -
               | 
| 4 | 
            +
              include Avo::ApplicationHelper
         | 
| 5 | 
            +
              attr_reader :pagy
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def initialize(resources: nil, resource: nil, reflection: nil, parent_model: nil, parent_resource: nil, pagy: nil, query: nil)
         | 
| 5 8 | 
             
                @resources = resources
         | 
| 6 9 | 
             
                @resource = resource
         | 
| 7 10 | 
             
                @reflection = reflection
         | 
| 8 11 | 
             
                @parent_model = parent_model
         | 
| 9 12 | 
             
                @parent_resource = parent_resource
         | 
| 13 | 
            +
                @pagy = pagy
         | 
| 14 | 
            +
                @query = query
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def encrypted_query
         | 
| 18 | 
            +
                return if @query.nil?
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                Avo::Services::EncryptionService.encrypt(
         | 
| 21 | 
            +
                  message: @query.to_sql,
         | 
| 22 | 
            +
                  purpose: :select_all
         | 
| 23 | 
            +
                )
         | 
| 10 24 | 
             
              end
         | 
| 11 25 | 
             
            end
         | 
| @@ -38,7 +38,7 @@ class Avo::ItemSwitcherComponent < Avo::BaseComponent | |
| 38 38 | 
             
              end
         | 
| 39 39 |  | 
| 40 40 | 
             
              def tab_group_component
         | 
| 41 | 
            -
                Avo::TabGroupComponent.new resource: @resource, group: item.hydrate(view: view), index: index, params: params, form: form, view: view
         | 
| 41 | 
            +
                Avo::TabGroupComponent.new resource: @resource, group: item.hydrate(view: view), index: index, params: params, form: form, view: view, tabs_style: item.style
         | 
| 42 42 | 
             
              end
         | 
| 43 43 |  | 
| 44 44 | 
             
              def field_component
         | 
| @@ -4,7 +4,6 @@ class Avo::ResourceComponent < Avo::BaseComponent | |
| 4 4 | 
             
              attr_reader :has_many_panels
         | 
| 5 5 | 
             
              attr_reader :has_as_belongs_to_many_panels
         | 
| 6 6 | 
             
              attr_reader :resource_tools
         | 
| 7 | 
            -
              attr_reader :resource
         | 
| 8 7 | 
             
              attr_reader :view
         | 
| 9 8 |  | 
| 10 9 | 
             
              def can_create?
         | 
| @@ -20,7 +19,7 @@ class Avo::ResourceComponent < Avo::BaseComponent | |
| 20 19 | 
             
              end
         | 
| 21 20 |  | 
| 22 21 | 
             
              def can_detach?
         | 
| 23 | 
            -
                authorize_association_for( | 
| 22 | 
            +
                authorize_association_for("detach")
         | 
| 24 23 | 
             
              end
         | 
| 25 24 |  | 
| 26 25 | 
             
              def detach_path
         | 
| @@ -54,7 +53,7 @@ class Avo::ResourceComponent < Avo::BaseComponent | |
| 54 53 |  | 
| 55 54 | 
             
                if @reflection.present?
         | 
| 56 55 | 
             
                  # Fetch the appropiate resource
         | 
| 57 | 
            -
                  reflection_resource =  | 
| 56 | 
            +
                  reflection_resource = ::Avo::App.get_resource_by_model_name(@reflection.active_record.name)
         | 
| 58 57 | 
             
                  # Fetch the model
         | 
| 59 58 | 
             
                  # Hydrate the resource with the model if we have one
         | 
| 60 59 | 
             
                  reflection_resource.hydrate(model: @parent_model) if @parent_model.present?
         | 
| @@ -15,7 +15,7 @@ | |
| 15 15 | 
             
                    data: {
         | 
| 16 16 | 
             
                      # Add a marker to know if we already loaded a turbo frame
         | 
| 17 17 | 
             
                      loaded: tab.name == active_tab_name,
         | 
| 18 | 
            -
                      tabs_target: : | 
| 18 | 
            +
                      tabs_target: :tabPanel,
         | 
| 19 19 | 
             
                      tab_id: tab.name,
         | 
| 20 20 | 
             
                    }
         | 
| 21 21 | 
             
                  }
         | 
| @@ -32,17 +32,17 @@ | |
| 32 32 | 
             
                  end
         | 
| 33 33 |  | 
| 34 34 | 
             
                  if should_lazy_load
         | 
| 35 | 
            -
                    args[:src] = helpers.resource_path(resource:  | 
| 35 | 
            +
                    args[:src] = helpers.resource_path(resource: resource, model: resource.model, keep_query_params: true, active_tab_name: tab.name, tab_turbo_frame: group.turbo_frame_id)
         | 
| 36 36 | 
             
                    args[:loading] = :lazy
         | 
| 37 37 | 
             
                  end
         | 
| 38 38 | 
             
                %>
         | 
| 39 39 | 
             
                <%= turbo_frame_tag tab.turbo_frame_id(parent: @group), **args do %>
         | 
| 40 40 | 
             
                  <div class="border rounded-lg p-2 -mx-2 -my-2 lg:p-4 lg:-mx-4 lg:-my-4 space-y-4">
         | 
| 41 | 
            -
                    <%= render Avo::TabSwitcherComponent.new resource:  | 
| 41 | 
            +
                    <%= render Avo::TabSwitcherComponent.new resource: resource, current_tab: tab, group: group, active_tab_name: active_tab_name, view: view, style: tabs_style %>
         | 
| 42 42 | 
             
                    <% if !should_lazy_load && !tab.empty? %>
         | 
| 43 43 | 
             
                      <div class="space-y-12">
         | 
| 44 44 | 
             
                        <% tab.visible_items.each do |item| %>
         | 
| 45 | 
            -
                          <%= render Avo::ItemSwitcherComponent.new resource:  | 
| 45 | 
            +
                          <%= render Avo::ItemSwitcherComponent.new resource: resource, item: item, index: index, form: form, view: @view %>
         | 
| 46 46 | 
             
                        <% end %>
         | 
| 47 47 | 
             
                      </div>
         | 
| 48 48 | 
             
                    <% end %>
         | 
| @@ -5,14 +5,16 @@ class Avo::TabGroupComponent < Avo::BaseComponent | |
| 5 5 | 
             
              attr_reader :index
         | 
| 6 6 | 
             
              attr_reader :view
         | 
| 7 7 | 
             
              attr_reader :form
         | 
| 8 | 
            +
              attr_reader :resource
         | 
| 8 9 |  | 
| 9 | 
            -
              def initialize(resource:, group:, index:, form:, params:, view:)
         | 
| 10 | 
            +
              def initialize(resource:, group:, index:, form:, params:, view:, tabs_style:)
         | 
| 10 11 | 
             
                @resource = resource
         | 
| 11 12 | 
             
                @group = group
         | 
| 12 13 | 
             
                @index = index
         | 
| 13 14 | 
             
                @form = form
         | 
| 14 15 | 
             
                @params = params
         | 
| 15 16 | 
             
                @view = view
         | 
| 17 | 
            +
                @tabs_style = tabs_style
         | 
| 16 18 |  | 
| 17 19 | 
             
                @group.index = index
         | 
| 18 20 | 
             
              end
         | 
| @@ -48,4 +50,8 @@ class Avo::TabGroupComponent < Avo::BaseComponent | |
| 48 50 | 
             
                  tab.name.to_s == active_tab_name.to_s
         | 
| 49 51 | 
             
                end
         | 
| 50 52 | 
             
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              def tabs_style
         | 
| 55 | 
            +
                @tabs_style || Avo.configuration.tabs_style
         | 
| 56 | 
            +
              end
         | 
| 51 57 | 
             
            end
         | 
| @@ -1,21 +1,41 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
              <div class=" | 
| 3 | 
            -
                 | 
| 4 | 
            -
                   | 
| 5 | 
            -
             | 
| 1 | 
            +
            <% if style == :tabs %>
         | 
| 2 | 
            +
              <div class="flex overflow-auto mac-styled-scrollbar" data-target="tab-switcher" data-style="tabs">
         | 
| 3 | 
            +
                <div class="button-group">
         | 
| 4 | 
            +
                  <% visible_items.each do |tab| %>
         | 
| 5 | 
            +
                    <%= a_link tab_path(tab),
         | 
| 6 | 
            +
                      color: selected?(tab) ? :primary : :gray ,
         | 
| 6 7 | 
             
                      rounded: false,
         | 
| 7 8 | 
             
                      size: :sm,
         | 
| 8 | 
            -
                      class: selected?(tab) ?  | 
| 9 | 
            +
                      class: "active:outline-0 #{selected?(tab) ? "z-20" : "bg-gray-100 border-gray-300"}",
         | 
| 9 10 | 
             
                      title: tab.description,
         | 
| 10 11 | 
             
                      data: {
         | 
| 11 12 | 
             
                        tippy: tab.description.present? ? 'tooltip' : '',
         | 
| 12 | 
            -
                        control: "view-type-toggle-#{tab.name}",
         | 
| 13 13 | 
             
                        selected: selected?(tab),
         | 
| 14 14 | 
             
                        action: 'click->tabs#changeTab',
         | 
| 15 15 | 
             
                        tabs_id_param: tab.name
         | 
| 16 16 | 
             
                      } do %>
         | 
| 17 | 
            +
                      <%= tab.name %>
         | 
| 18 | 
            +
                    <% end %>
         | 
| 19 | 
            +
                  <% end %>
         | 
| 20 | 
            +
                </div>
         | 
| 21 | 
            +
              </div>
         | 
| 22 | 
            +
            <% else %>
         | 
| 23 | 
            +
              <div class="flex flex-wrap gap-2" data-target="tab-switcher" data-style="pills">
         | 
| 24 | 
            +
                <% visible_items.each do |tab| %>
         | 
| 25 | 
            +
                  <%= a_link tab_path(tab),
         | 
| 26 | 
            +
                  color: selected?(tab) ? :primary : :gray,
         | 
| 27 | 
            +
                  style: selected?(tab) ? :outline : :text,
         | 
| 28 | 
            +
                  size: :sm,
         | 
| 29 | 
            +
                  class: selected?(tab) ? "z-20" : "",
         | 
| 30 | 
            +
                  title: tab.description,
         | 
| 31 | 
            +
                  data: {
         | 
| 32 | 
            +
                    tippy: tab.description.present? ? 'tooltip' : '',
         | 
| 33 | 
            +
                    selected: selected?(tab),
         | 
| 34 | 
            +
                    action: 'click->tabs#changeTab',
         | 
| 35 | 
            +
                    tabs_id_param: tab.name
         | 
| 36 | 
            +
                  } do %>
         | 
| 17 37 | 
             
                    <%= tab.name %>
         | 
| 18 38 | 
             
                  <% end %>
         | 
| 19 39 | 
             
                <% end %>
         | 
| 20 40 | 
             
              </div>
         | 
| 21 | 
            -
             | 
| 41 | 
            +
            <% end %>
         | 
| @@ -9,14 +9,16 @@ class Avo::TabSwitcherComponent < Avo::BaseComponent | |
| 9 9 | 
             
              attr_reader :current_tab
         | 
| 10 10 | 
             
              attr_reader :tabs
         | 
| 11 11 | 
             
              attr_reader :view
         | 
| 12 | 
            +
              attr_reader :style
         | 
| 12 13 |  | 
| 13 | 
            -
              def initialize(resource:, group:, current_tab:, active_tab_name:, view:)
         | 
| 14 | 
            +
              def initialize(resource:, group:, current_tab:, active_tab_name:, view:, style:)
         | 
| 14 15 | 
             
                @active_tab_name = active_tab_name
         | 
| 15 16 | 
             
                @resource = resource
         | 
| 16 17 | 
             
                @group = group
         | 
| 17 18 | 
             
                @current_tab = current_tab
         | 
| 18 19 | 
             
                @tabs = group.items
         | 
| 19 20 | 
             
                @view = view
         | 
| 21 | 
            +
                @style = style
         | 
| 20 22 | 
             
              end
         | 
| 21 23 |  | 
| 22 24 | 
             
              def tab_path(tab)
         | 
| @@ -28,10 +28,7 @@ | |
| 28 28 | 
             
                  <% end %>
         | 
| 29 29 | 
             
                <% end %>
         | 
| 30 30 | 
             
                <% c.body do %>
         | 
| 31 | 
            -
                  <div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' if @resource.search_query.nil? && @filters.empty? && available_view_types.count <= 1 %>"
         | 
| 32 | 
            -
                    data-selected-resources-name="<%= @resource.model_key %>"
         | 
| 33 | 
            -
                    data-selected-resources="[]"
         | 
| 34 | 
            -
                  >
         | 
| 31 | 
            +
                  <div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' if @resource.search_query.nil? && @filters.empty? && available_view_types.count <= 1 %>">
         | 
| 35 32 | 
             
                    <% unless hide_search_input %>
         | 
| 36 33 | 
             
                      <div class="flex items-center px-4 w-64">
         | 
| 37 34 | 
             
                        <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.route_key, via_reflection: via_reflection} %>
         | 
| @@ -42,7 +39,7 @@ | |
| 42 39 | 
             
                    <% end %>
         | 
| 43 40 | 
             
                    <% if @filters.present? || available_view_types.count > 1 %>
         | 
| 44 41 | 
             
                      <div class="justify-self-end flex justify-start xs:justify-end items-center px-4 space-x-3">
         | 
| 45 | 
            -
                        <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters %>
         | 
| 42 | 
            +
                        <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters, parent_model: @parent_model %>
         | 
| 46 43 | 
             
                        <%= render partial: 'avo/partials/view_toggle_button', locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: @turbo_frame } if available_view_types.count > 1 %>
         | 
| 47 44 | 
             
                      </div>
         | 
| 48 45 | 
             
                    <% end %>
         | 
| @@ -51,7 +48,7 @@ | |
| 51 48 | 
             
                    <% if @resources.present? %>
         | 
| 52 49 | 
             
                      <div class="w-full overflow-auto flex flex-col mt-0 mac-styled-scrollbar">
         | 
| 53 50 | 
             
                        <div class="relative flex-1 flex">
         | 
| 54 | 
            -
                          <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource)) %>
         | 
| 51 | 
            +
                          <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
         | 
| 55 52 | 
             
                        </div>
         | 
| 56 53 | 
             
                      </div>
         | 
| 57 54 | 
             
                    <% else %>
         | 
| @@ -16,7 +16,8 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent | |
| 16 16 | 
             
                turbo_frame: "",
         | 
| 17 17 | 
             
                parent_model: nil,
         | 
| 18 18 | 
             
                parent_resource: nil,
         | 
| 19 | 
            -
                applied_filters: []
         | 
| 19 | 
            +
                applied_filters: [],
         | 
| 20 | 
            +
                query: nil
         | 
| 20 21 | 
             
              )
         | 
| 21 22 | 
             
                @resource = resource
         | 
| 22 23 | 
             
                @resources = resources
         | 
| @@ -31,6 +32,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent | |
| 31 32 | 
             
                @parent_resource = parent_resource
         | 
| 32 33 | 
             
                @applied_filters = applied_filters
         | 
| 33 34 | 
             
                @view = :index
         | 
| 35 | 
            +
                @query = query
         | 
| 34 36 | 
             
              end
         | 
| 35 37 |  | 
| 36 38 | 
             
              def title
         | 
| @@ -146,5 +148,4 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent | |
| 146 148 | 
             
                  id: @parent_model.id
         | 
| 147 149 | 
             
                }
         | 
| 148 150 | 
             
              end
         | 
| 149 | 
            -
             | 
| 150 151 | 
             
            end
         | 
| @@ -11,10 +11,10 @@ module Avo | |
| 11 11 | 
             
                end
         | 
| 12 12 |  | 
| 13 13 | 
             
                def handle
         | 
| 14 | 
            -
                  resource_ids = action_params[:fields][: | 
| 15 | 
            -
                   | 
| 14 | 
            +
                  resource_ids = action_params[:fields][:avo_resource_ids].split(",")
         | 
| 15 | 
            +
                  @selected_query = action_params[:fields][:avo_selected_query]
         | 
| 16 16 |  | 
| 17 | 
            -
                  fields = action_params[:fields].except( | 
| 17 | 
            +
                  fields = action_params[:fields].except(:avo_resource_ids, :avo_selected_query)
         | 
| 18 18 |  | 
| 19 19 | 
             
                  args = {
         | 
| 20 20 | 
             
                    fields: fields,
         | 
| @@ -22,7 +22,13 @@ module Avo | |
| 22 22 | 
             
                    resource: resource
         | 
| 23 23 | 
             
                  }
         | 
| 24 24 |  | 
| 25 | 
            -
                   | 
| 25 | 
            +
                  unless @action.standalone
         | 
| 26 | 
            +
                    args[:models] = if @selected_query.present?
         | 
| 27 | 
            +
                      @resource.model_class.find_by_sql decrypted_query
         | 
| 28 | 
            +
                    else
         | 
| 29 | 
            +
                      @resource.class.find_scope.find resource_ids
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 26 32 |  | 
| 27 33 | 
             
                  performed_action = @action.handle_action(**args)
         | 
| 28 34 |  | 
| @@ -90,5 +96,12 @@ module Avo | |
| 90 96 | 
             
                    message[:type] != :silent
         | 
| 91 97 | 
             
                  end
         | 
| 92 98 | 
             
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def decrypted_query
         | 
| 101 | 
            +
                  Avo::Services::EncryptionService.decrypt(
         | 
| 102 | 
            +
                    message: @selected_query,
         | 
| 103 | 
            +
                    purpose: :select_all
         | 
| 104 | 
            +
                  )
         | 
| 105 | 
            +
                end
         | 
| 93 106 | 
             
              end
         | 
| 94 107 | 
             
            end
         | 
| @@ -48,7 +48,7 @@ module Avo | |
| 48 48 | 
             
                def item_selector_input(floating: false, size: :md)
         | 
| 49 49 | 
             
                  "<input type='checkbox'
         | 
| 50 50 | 
             
                    class='mx-3 rounded #{"absolute inset-auto left-0 mt-2 z-10 hidden group-hover:block checked:block" if floating} #{size.to_sym == :lg ? "w-5 h-5" : "w-4 h-4"}'
         | 
| 51 | 
            -
                    data-action='input->item-selector#toggle input->item-select-all# | 
| 51 | 
            +
                    data-action='input->item-selector#toggle input->item-select-all#selectRow'
         | 
| 52 52 | 
             
                    data-item-select-all-target='itemCheckbox'
         | 
| 53 53 | 
             
                    name='#{t "avo.select_item"}'
         | 
| 54 54 | 
             
                    title='#{t "avo.select_item"}'
         | 
| @@ -2,11 +2,16 @@ import { Controller } from '@hotwired/stimulus' | |
| 2 2 | 
             
            import { castBoolean } from '../helpers/cast_boolean'
         | 
| 3 3 |  | 
| 4 4 | 
             
            export default class extends Controller {
         | 
| 5 | 
            -
              static targets = ['controllerDiv', 'resourceIds', 'form']
         | 
| 5 | 
            +
              static targets = ['controllerDiv', 'resourceIds', 'form', 'selectedAllQuery']
         | 
| 6 6 |  | 
| 7 7 | 
             
              connect() {
         | 
| 8 8 | 
             
                this.resourceIdsTarget.value = this.resourceIds
         | 
| 9 9 |  | 
| 10 | 
            +
                // This value is picked up from the DOM so we check true/false as strings
         | 
| 11 | 
            +
                if (this.selectionOptions.itemSelectAllSelectedAllValue === 'true') {
         | 
| 12 | 
            +
                  this.selectedAllQueryTarget.value = this.selectionOptions.itemSelectAllSelectedAllQueryValue
         | 
| 13 | 
            +
                }
         | 
| 14 | 
            +
             | 
| 10 15 | 
             
                if (this.noConfirmation) {
         | 
| 11 16 | 
             
                  this.formTarget.submit()
         | 
| 12 17 | 
             
                } else {
         | 
| @@ -24,9 +29,13 @@ export default class extends Controller { | |
| 24 29 |  | 
| 25 30 | 
             
              get resourceIds() {
         | 
| 26 31 | 
             
                try {
         | 
| 27 | 
            -
                  return JSON.parse( | 
| 32 | 
            +
                  return JSON.parse(this.selectionOptions.selectedResources)
         | 
| 28 33 | 
             
                } catch (error) {
         | 
| 29 34 | 
             
                  return []
         | 
| 30 35 | 
             
                }
         | 
| 31 36 | 
             
              }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              get selectionOptions() {
         | 
| 39 | 
            +
                return document.querySelector(`[data-selected-resources-name="${this.resourceName}"]`).dataset
         | 
| 40 | 
            +
              }
         | 
| 32 41 | 
             
            }
         | 
| @@ -117,7 +117,11 @@ export default class extends Controller { | |
| 117 117 |  | 
| 118 118 | 
             
                flatpickr(this.fakeInputTarget, options)
         | 
| 119 119 |  | 
| 120 | 
            -
                 | 
| 120 | 
            +
                if (this.enableTimeValue) {
         | 
| 121 | 
            +
                  this.updateRealInput(this.parsedValue.setZone(this.displayTimezone).toISO())
         | 
| 122 | 
            +
                } else {
         | 
| 123 | 
            +
                  this.updateRealInput(universalTimestamp(this.initialValue))
         | 
| 124 | 
            +
                }
         | 
| 121 125 | 
             
              }
         | 
| 122 126 |  | 
| 123 127 | 
             
              onChange(selectedDates) {
         |