madmin 1.0.1 → 1.2.1

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -1
  3. data/app/controllers/madmin/base_controller.rb +0 -5
  4. data/app/controllers/madmin/resource_controller.rb +23 -2
  5. data/app/helpers/madmin/application_helper.rb +4 -0
  6. data/app/helpers/madmin/nav_helper.rb +30 -0
  7. data/app/helpers/madmin/sort_helper.rb +32 -0
  8. data/app/views/layouts/madmin/application.html.erb +5 -5
  9. data/app/views/madmin/application/_form.html.erb +6 -8
  10. data/app/views/madmin/application/_javascript.html.erb +73 -7
  11. data/app/views/madmin/application/_navigation.html.erb +31 -5
  12. data/app/views/madmin/application/edit.html.erb +5 -1
  13. data/app/views/madmin/application/index.html.erb +34 -22
  14. data/app/views/madmin/application/new.html.erb +5 -1
  15. data/app/views/madmin/application/show.html.erb +24 -17
  16. data/app/views/madmin/fields/attachment/_form.html.erb +3 -1
  17. data/app/views/madmin/fields/attachment/_show.html.erb +7 -1
  18. data/app/views/madmin/fields/attachments/_form.html.erb +3 -1
  19. data/app/views/madmin/fields/attachments/_show.html.erb +7 -3
  20. data/app/views/madmin/fields/belongs_to/_form.html.erb +4 -2
  21. data/app/views/madmin/fields/belongs_to/_show.html.erb +1 -1
  22. data/app/views/madmin/fields/boolean/_form.html.erb +3 -1
  23. data/app/views/madmin/fields/date/_form.html.erb +3 -1
  24. data/app/views/madmin/fields/date_time/_form.html.erb +3 -1
  25. data/app/views/madmin/fields/decimal/_form.html.erb +3 -1
  26. data/app/views/madmin/fields/enum/_form.html.erb +4 -2
  27. data/app/views/madmin/fields/float/_form.html.erb +3 -1
  28. data/app/views/madmin/fields/has_many/_form.html.erb +4 -2
  29. data/app/views/madmin/fields/has_many/_show.html.erb +1 -1
  30. data/app/views/madmin/fields/has_one/_form.html.erb +3 -2
  31. data/app/views/madmin/fields/integer/_form.html.erb +3 -1
  32. data/app/views/madmin/fields/json/_form.html.erb +3 -1
  33. data/app/views/madmin/fields/nested_has_many/_fields.html.erb +18 -0
  34. data/app/views/madmin/fields/nested_has_many/_form.html.erb +32 -0
  35. data/app/views/madmin/fields/nested_has_many/_index.html.erb +1 -0
  36. data/app/views/madmin/fields/nested_has_many/_show.html.erb +5 -0
  37. data/app/views/madmin/fields/password/_form.html.erb +4 -0
  38. data/app/views/madmin/fields/password/_index.html.erb +1 -0
  39. data/app/views/madmin/fields/password/_show.html.erb +1 -0
  40. data/app/views/madmin/fields/polymorphic/_form.html.erb +3 -1
  41. data/app/views/madmin/fields/polymorphic/_show.html.erb +1 -1
  42. data/app/views/madmin/fields/rich_text/_form.html.erb +3 -1
  43. data/app/views/madmin/fields/string/_form.html.erb +3 -1
  44. data/app/views/madmin/fields/text/_form.html.erb +3 -1
  45. data/app/views/madmin/fields/time/_form.html.erb +3 -1
  46. data/app/views/madmin/shared/_label.html.erb +4 -0
  47. data/lib/generators/madmin/field/field_generator.rb +31 -0
  48. data/lib/generators/madmin/field/templates/_form.html.erb +2 -0
  49. data/lib/generators/madmin/field/templates/_index.html.erb +1 -0
  50. data/lib/generators/madmin/field/templates/_show.html.erb +1 -0
  51. data/lib/generators/madmin/field/templates/field.rb.tt +26 -0
  52. data/lib/generators/madmin/install/install_generator.rb +6 -1
  53. data/lib/generators/madmin/install/templates/routes.rb.tt +3 -0
  54. data/lib/generators/madmin/resource/resource_generator.rb +15 -4
  55. data/lib/generators/madmin/resource/templates/controller.rb.tt +6 -0
  56. data/lib/generators/madmin/resource/templates/resource.rb.tt +14 -0
  57. data/lib/generators/madmin/views/javascript_generator.rb +15 -0
  58. data/lib/generators/madmin/views/views_generator.rb +6 -5
  59. data/lib/madmin/engine.rb +1 -1
  60. data/lib/madmin/field.rb +4 -0
  61. data/lib/madmin/fields/belongs_to.rb +9 -5
  62. data/lib/madmin/fields/has_many.rb +10 -5
  63. data/lib/madmin/fields/nested_has_many.rb +40 -0
  64. data/lib/madmin/fields/password.rb +6 -0
  65. data/lib/madmin/fields/string.rb +3 -0
  66. data/lib/madmin/fields/text.rb +3 -0
  67. data/lib/madmin/generator_helpers.rb +22 -4
  68. data/lib/madmin/resource.rb +53 -19
  69. data/lib/madmin/search.rb +60 -0
  70. data/lib/madmin/version.rb +1 -1
  71. data/lib/madmin.rb +30 -14
  72. metadata +25 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f149452559626564a6b3888b89ddeaf7fb2bf1c3fedbbc4ac4ba3dc2a1df4e58
4
- data.tar.gz: 8536e397b8d032d704b7e10c938af1ee1eda4911d9c6933ecd9c11f68fbac8f7
3
+ metadata.gz: 1f3bd8e986e4859620b06401c531dc24ca84bb02e4e2284a34918b5a088c126c
4
+ data.tar.gz: 9bf59b4f93849feda832005caa748e02b4227ea2552cd6e94264d3b7d170f5a9
5
5
  SHA512:
6
- metadata.gz: e6a4a123086ee55711728e9f2b1bff5ca90d28c7238690d5f51e9b834f1f5eb15170b7e8e3b1aac7a79e08d4b55ee2500dc11f16d4f62c4a31cc4d0d5ab0b6ba
7
- data.tar.gz: 49a20396c04c22fbe8001d53ed4cc567b441171943a090cd888b98e94cbf11b31ad5ec89fc67dd9cae98af03282adf8bdacec159075c73605ac743c994338e51
6
+ metadata.gz: 3181e8f05aad7af36b905a4a04ffb2ddd63419b347e699b0b9ea6d8d2fb9a880f12ebbd7b12d05770e02dc7508e4789a1cb8250b11b6eed26878b6ef71d7fc1b
7
+ data.tar.gz: '0019a725b02cde9e007190917381da718a139662d1d9094a0e49ccc69b799437865a4170658b99492947ce1d6d2ae04f71dde97886212a8dd728c3a9aa2275f3'
data/README.md CHANGED
@@ -7,9 +7,12 @@
7
7
  Why another Ruby on Rails admin? We wanted an admin that was:
8
8
 
9
9
  * Familiar and customizable like Rails scaffolds (less DSL)
10
- * Supports all the Rails features out of the box (ActionText, ActionMailbox, etc)
10
+ * Supports all the Rails features out of the box (ActionText, ActionMailbox, has_secure_password, etc)
11
11
  * Stimulus / Turbolinks / Hotwire ready
12
12
 
13
+ ![Madmin Screenshot](docs/images/screenshot.png)
14
+ _We're still working on the design!_
15
+
13
16
  ## Installation
14
17
  Add `madmin` to your application's Gemfile:
15
18
 
@@ -76,6 +79,31 @@ rails generate madmin:views:index Book
76
79
  # -> app/views/madmin/books/index.html.erb
77
80
  ```
78
81
 
82
+ ## Custom Fields
83
+
84
+ You can generate a custom field with:
85
+
86
+ ```bash
87
+ rails g madmin:field Custom
88
+ ```
89
+
90
+ This will create a `CustomField` class in `app/madmin/fields/custom_field.rb`
91
+ And the related views:
92
+
93
+ ```bash
94
+ # -> app/views/madmin/fields/custom_field/_form.html.erb
95
+ # -> app/views/madmin/fields/custom_field/_index.html.erb
96
+ # -> app/views/madmin/fields/custom_field/_show.html.erb
97
+ ```
98
+
99
+ You can then use this field on our resource:
100
+
101
+ ```ruby
102
+ class PostResource < Madmin::Resource
103
+ attribute :title, field: CustomField
104
+ end
105
+ ```
106
+
79
107
  ## Authentication
80
108
 
81
109
  You can use a couple of strategies to authenticate users who are trying to
@@ -3,10 +3,5 @@ module Madmin
3
3
  include Pagy::Backend
4
4
 
5
5
  protect_from_forgery with: :exception
6
-
7
- # Loads all the models for the sidebar
8
- before_action do
9
- Rails.application.eager_load!
10
- end
11
6
  end
12
7
  end
@@ -1,9 +1,24 @@
1
1
  module Madmin
2
2
  class ResourceController < ApplicationController
3
+ include SortHelper
4
+
3
5
  before_action :set_record, except: [:index, :new, :create]
4
6
 
7
+ # Assign current_user for paper_trail gem
8
+ before_action :set_paper_trail_whodunnit, if: -> { respond_to?(:set_paper_trail_whodunnit, true) }
9
+
5
10
  def index
6
11
  @pagy, @records = pagy(scoped_resources)
12
+
13
+ respond_to do |format|
14
+ format.html
15
+ format.json {
16
+ render json: @records.map { |r| {name: @resource.display_name(r), id: r.id} }
17
+ }
18
+ end
19
+ rescue Pagy::OverflowError
20
+ params[:page] = 1
21
+ retry
7
22
  end
8
23
 
9
24
  def show
@@ -41,7 +56,7 @@ module Madmin
41
56
  private
42
57
 
43
58
  def set_record
44
- @record = resource.model.find(params[:id])
59
+ @record = resource.model_find(params[:id])
45
60
  end
46
61
 
47
62
  def resource
@@ -54,7 +69,9 @@ module Madmin
54
69
  end
55
70
 
56
71
  def scoped_resources
57
- resource.model.send(valid_scope)
72
+ resources = resource.model.send(valid_scope)
73
+ resources = Madmin::Search.new(resources, resource, search_term).run
74
+ resources.reorder(sort_column => sort_direction)
58
75
  end
59
76
 
60
77
  def valid_scope
@@ -77,5 +94,9 @@ module Madmin
77
94
  raise "Unrecognised param data: #{data.inspect}"
78
95
  end
79
96
  end
97
+
98
+ def search_term
99
+ @search_term ||= params[:q].to_s.strip
100
+ end
80
101
  end
81
102
  end
@@ -13,5 +13,9 @@ module Madmin
13
13
  version += "-#{Rails::VERSION::PRE}" if Rails::VERSION::PRE
14
14
  version
15
15
  end
16
+
17
+ def clear_search_params
18
+ params.except(:q, :_page).permit(:page, :sort, :direction)
19
+ end
16
20
  end
17
21
  end
@@ -0,0 +1,30 @@
1
+ module Madmin::NavHelper
2
+ def nav_link_to(name = nil, options = {}, html_options = {}, &block)
3
+ if block
4
+ html_options = options
5
+ options = name
6
+ name = block
7
+ end
8
+
9
+ url = url_for(options)
10
+ starts_with = html_options.delete(:starts_with)
11
+ html_options[:class] = Array.wrap(html_options[:class])
12
+ active_class = html_options.delete(:active_class) || "active"
13
+ inactive_class = html_options.delete(:inactive_class) || ""
14
+
15
+ active = if (paths = Array.wrap(starts_with)) && paths.present?
16
+ paths.any? { |path| request.path.start_with?(path) }
17
+ else
18
+ request.path == url
19
+ end
20
+
21
+ classes = active ? active_class : inactive_class
22
+ html_options[:class] << classes unless classes.empty?
23
+
24
+ html_options.except!(:class) if html_options[:class].empty?
25
+
26
+ return link_to url, html_options, &block if block
27
+
28
+ link_to name, url, html_options
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ module Madmin
2
+ module SortHelper
3
+ def sortable(column, title, options = {})
4
+ matching_column = (column.to_s == sort_column)
5
+ direction = sort_direction == "asc" ? "desc" : "asc"
6
+
7
+ link_to request.params.merge(sort: column, direction: direction), options do
8
+ concat title
9
+ if matching_column
10
+ concat " "
11
+ concat tag.i(sort_direction == "asc" ? "▲" : "▼")
12
+ end
13
+ end
14
+ end
15
+
16
+ def sort_column
17
+ resource.sortable_columns.include?(params[:sort]) ? params[:sort] : default_sort_column
18
+ end
19
+
20
+ def sort_direction
21
+ ["asc", "desc"].include?(params[:direction]) ? params[:direction] : default_sort_direction
22
+ end
23
+
24
+ def default_sort_column
25
+ resource.try(:default_sort_column) || (["created_at", "id", "uuid"] & resource.model.column_names).first
26
+ end
27
+
28
+ def default_sort_direction
29
+ resource.try(:default_sort_direction) || "desc"
30
+ end
31
+ end
32
+ end
@@ -7,19 +7,19 @@
7
7
  <title>
8
8
  Madmin: <%= Rails.application.class %>
9
9
  </title>
10
- <link href="https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css" rel="stylesheet" />
11
10
  <link href="https://unpkg.com/@tailwindcss/forms/dist/forms.min.css" rel="stylesheet" />
11
+ <link href="https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css" rel="stylesheet" />
12
12
  <link href="https://unpkg.com/@tailwindcss/typography/dist/typography.min.css" rel="stylesheet" />
13
13
  <%= csrf_meta_tags %>
14
14
 
15
15
  <%= render "javascript" %>
16
16
  </head>
17
- <body class="prose" style="max-width:none">
18
- <div class="flex w-full p-4">
19
- <div id="sidebar" class="w-64 flex-shrink-0">
17
+ <body class="min-h-screen">
18
+ <div class="md:flex w-full min-h-screen">
19
+ <div id="sidebar" class="md:w-64 p-4 flex-shrink-0 border-r">
20
20
  <%= render "navigation" -%>
21
21
  </div>
22
- <main class="w-full" role="main">
22
+ <main class="flex-grow p-4" role="main">
23
23
  <%#= render "flashes" -%>
24
24
  <%= yield %>
25
25
  </main>
@@ -9,15 +9,13 @@
9
9
  </div>
10
10
  <% end %>
11
11
 
12
- <% resource.attributes.each do |attribute| %>
13
- <% next if attribute[:field].nil? %>
14
- <% next unless attribute[:field].visible?(action_name) %>
15
- <% next unless attribute[:field].visible?(:form) %>
12
+ <% resource.attributes.values.each do |attribute| %>
13
+ <% next if attribute.field.nil? %>
14
+ <% next unless attribute.field.visible?(action_name) %>
15
+ <% next unless attribute.field.visible?(:form) %>
16
16
 
17
- <% field = attribute[:field] %>
18
-
19
- <div class="mb-4 flex">
20
- <%= render partial: field.to_partial_path("form"), locals: { field: field, record: record, form: form, resource: resource } %>
17
+ <div class="mb-4 md:flex">
18
+ <%= render partial: attribute.field.to_partial_path("form"), locals: { field: attribute.field, record: record, form: form, resource: resource } %>
21
19
  </div>
22
20
  <% end %>
23
21
 
@@ -1,24 +1,90 @@
1
- <%= stylesheet_link_tag "https://cdn.skypack.dev/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
2
- <%= stylesheet_link_tag "https://cdn.skypack.dev/slim-select/dist/slimselect.min.css", "data-turbo-track": "reload" %>
3
- <%= stylesheet_link_tag "https://cdn.skypack.dev/trix/dist/trix.css", "data-turbo-track": "reload" %>
1
+ <%= stylesheet_link_tag "https://unpkg.com/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
2
+ <%= stylesheet_link_tag "https://unpkg.com/trix/dist/trix.css", "data-turbo-track": "reload" %>
3
+ <%= stylesheet_link_tag "https://unpkg.com/tom-select/dist/css/tom-select.min.css", "data-turbo-track": "reload" %>
4
4
 
5
5
  <script type="module">
6
- import { Application } from 'https://cdn.skypack.dev/stimulus'
6
+ import { Application, Controller } from 'https://cdn.skypack.dev/stimulus'
7
7
  const application = Application.start()
8
8
 
9
+ import { Dropdown } from "https://cdn.skypack.dev/tailwindcss-stimulus-components"
10
+ application.register("dropdown", Dropdown)
11
+
9
12
  import stimulusFlatpickr from 'https://cdn.skypack.dev/stimulus-flatpickr'
10
13
  application.register("flatpickr", stimulusFlatpickr)
11
14
 
12
- import stimulusSlimselect from 'https://cdn.skypack.dev/stimulus-slimselect'
13
- application.register("slimselect", stimulusSlimselect)
15
+ import TomSelect from 'https://cdn.skypack.dev/tom-select'
14
16
 
15
17
  import Rails from 'https://cdn.skypack.dev/@rails/ujs@<%= npm_rails_version %>'
16
18
  import * as ActiveStorage from 'https://cdn.skypack.dev/@rails/activestorage@<%= npm_rails_version %>'
17
19
  import 'https://cdn.skypack.dev/trix'
18
20
  import 'https://cdn.skypack.dev/@rails/actiontext@<%= npm_rails_version %>'
19
21
 
20
- Rails.start()
22
+ if (!window._rails_loaded) { Rails.start() }
21
23
  ActiveStorage.start()
22
24
 
23
25
  import * as Turbo from "https://cdn.skypack.dev/@hotwired/turbo"
26
+
27
+ (() => {
28
+ application.register('select', class extends Controller {
29
+ static values = {
30
+ options: Object,
31
+ url: String
32
+ }
33
+
34
+ connect() {
35
+ this.select = new TomSelect(this.element, {
36
+ plugins: ['remove_button'],
37
+ valueField: 'id',
38
+ labelField: 'name',
39
+ searchField: 'name',
40
+ load: (search, callback) => {
41
+ fetch(this.urlValue)
42
+ .then(response => response.json())
43
+ .then(json => {
44
+ callback(json);
45
+ }).catch(() => {
46
+ callback();
47
+ });
48
+ }
49
+ })
50
+ }
51
+
52
+ disconnect() {
53
+ this.select.destroy()
54
+ }
55
+ })
56
+
57
+ application.register('nested-form', class extends Controller {
58
+ static get targets() {
59
+ return [ "links", "template" ]
60
+ }
61
+
62
+ connect() {
63
+ this.wrapperClass = this.data.get("wrapperClass") || "nested-fields"
64
+ }
65
+
66
+ add_association(event) {
67
+ event.preventDefault()
68
+
69
+ var content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
70
+ this.linksTarget.insertAdjacentHTML('beforebegin', content)
71
+ }
72
+
73
+ remove_association(event) {
74
+ event.preventDefault()
75
+
76
+ let wrapper = event.target.closest("." + this.wrapperClass)
77
+
78
+ // New records are simply removed from the page
79
+ if (wrapper.dataset.newRecord == "true") {
80
+ wrapper.remove()
81
+
82
+ // Existing records are hidden and flagged for deletion
83
+ } else {
84
+ wrapper.querySelector("input[name*='_destroy']").value = 1
85
+ wrapper.style.display = 'none'
86
+ }
87
+ }
88
+ })
89
+ })()
24
90
  </script>
@@ -1,6 +1,32 @@
1
- <div class="text-sm">
2
- <%= link_to " Back to App", main_app.root_url, class: "block p-1", data: { turbo: false } if main_app.respond_to?(:root_url) %>
3
- <% Madmin.resources.each do |resource| %>
4
- <%= link_to resource.friendly_name.pluralize, resource.index_path, class: "block p-1" %>
5
- <% end %>
1
+ <div class="flex flex-col h-full text-sm" data-controller="dropdown">
2
+ <div class="flex md:block justify-between items-center">
3
+ <div class="flex md:block items-center">
4
+ <h1 class="mr-2 md:p-2 text-xl font-semibold">Madmin</h1>
5
+ <% if main_app.respond_to?(:root_url) %>
6
+ <%= link_to main_app.root_url, class: "block p-2 rounded hover:bg-gray-200", data: { turbo: false } do %>
7
+ ← Back <span class="hidden md:inline">to App</span>
8
+ <% end %>
9
+ <% end %>
10
+ </div>
11
+
12
+ <div class="-mr-2 flex items-center md:hidden">
13
+ <button data-action="click->dropdown#toggle touch->dropdown#toggle click@window->dropdown#hide touch@window#dropdown->hide" type="button" class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:bg-gray-200 focus:outline-none focus:ring-2 focus-ring-inset focus:ring-white" id="main-menu" aria-haspopup="true">
14
+ <span class="sr-only">Open main menu</span>
15
+ <!-- Heroicon name: outline/menu -->
16
+ <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
17
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
18
+ </svg>
19
+ </button>
20
+ </div>
21
+ </div>
22
+
23
+ <div class="hidden md:flex flex-col flex-grow justify-between" data-dropdown-target="menu">
24
+ <% Madmin.resources.each do |resource| %>
25
+ <%= nav_link_to resource.friendly_name.pluralize, resource.index_path, class: "block p-2 rounded hover:bg-gray-100", starts_with: resource.index_path, active_class: "font-bold text-black" %>
26
+ <% end %>
27
+
28
+ <div class="mt-auto">
29
+ <%= link_to "View Madmin on GitHub", "https://github.com/excid3/madmin", target: :_blank, class: "block p-2 rounded text-gray-500 hover:bg-gray-100" %>
30
+ </div>
31
+ </div>
6
32
  </div>
@@ -1,3 +1,7 @@
1
- <h1><%= link_to resource.friendly_name.pluralize, resource.index_path %> / Edit <%= resource.friendly_name %> #<%= @record.id %></h1>
1
+ <h1 class="text-xl mb-4">
2
+ <%= link_to resource.friendly_name.pluralize, resource.index_path, class: "text-indigo-500" %>
3
+ /
4
+ <strong>Edit <%= link_to resource.display_name(@record), resource.show_path(@record), class: "text-indigo-500" %></strong>
5
+ </h1>
2
6
 
3
7
  <%= render partial: "form", locals: { record: @record, resource: resource } %>
@@ -1,44 +1,56 @@
1
- <div class="flex justify-between">
2
- <h1><%= resource.friendly_name.pluralize %></h1>
3
- <%= link_to "New #{resource.friendly_name}", resource.new_path %>
1
+ <div class="md:flex justify-between items-center space-y-4 md:space-y-0">
2
+ <h1 class="text-xl font-semibold"><%= resource.friendly_name.pluralize %></h1>
3
+
4
+ <div class="flex-grow flex md:justify-end gap-4">
5
+ <form class="flex items-center gap-2 relative">
6
+ <%= hidden_field_tag :page, params[:page], value: 1, class: "hidden" %>
7
+ <%= search_field_tag :q, params[:q], placeholder: "Search", class: "rounded-full px-4 focus:bg-white focus:border-indigo-500" %>
8
+ <%= link_to clear_search_params, class: "absolute top-1/2 right-3 text-gray-500 bg-white transform -translate-y-1/2" do %>
9
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
10
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
11
+ </svg>
12
+ <% end %>
13
+ </form>
14
+
15
+ <%= link_to resource.new_path, class: "bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow" do %>
16
+ New <span class="hidden md:inline"><%= resource.friendly_name %></span>
17
+ <% end %>
18
+ </div>
4
19
  </div>
5
20
 
6
- <div>
21
+ <div class="mb-4">
7
22
  <% if resource.scopes.any? %>
8
- <%= link_to "All", resource.index_path %>
23
+ <%= link_to "All", resource.index_path, class: class_names("p-2 rounded", {"bg-gray-100" => params[:scope].blank?}) %>
9
24
  <% end %>
10
25
 
11
26
  <% resource.scopes.each do |scope| %>
12
- <%= link_to scope.to_s.humanize, resource.index_path(scope: scope) %>
27
+ <%= link_to scope.to_s.humanize, resource.index_path(scope: scope), class: class_names("p-2 rounded", {"bg-gray-100" => params[:scope] == scope.to_s}) %>
13
28
  <% end %>
14
29
  </div>
15
30
 
16
- <table class="table-auto">
31
+ <table class="min-w-full divide-y divide-gray-200">
17
32
  <thead>
18
- <tr>
19
- <% resource.attributes.each do |attribute| %>
20
- <% next if attribute[:field].nil? %>
21
- <% next unless attribute[:field].visible?(action_name) %>
33
+ <tr class="border-b border-gray-200">
34
+ <% resource.attributes.values.each do |attribute| %>
35
+ <% next if attribute.field.nil? %>
36
+ <% next unless attribute.field.visible?(action_name) %>
22
37
 
23
- <th><%= attribute[:name].to_s.titleize %></th>
38
+ <th class="py-2 px-4 text-left text-xs text-gray-500 font-medium uppercase whitespace-nowrap"><%= sortable attribute.name, attribute.name.to_s.titleize %></th>
24
39
  <% end %>
25
- <th>Actions</th>
40
+ <th class="py-2 px-4 text-left text-xs text-gray-500 font-medium uppercase">Actions</th>
26
41
  </tr>
27
42
  </thead>
28
43
 
29
- <tbody>
44
+ <tbody class="text-sm divide-y">
30
45
  <% @records.each do |record| %>
31
46
  <tr>
32
- <% resource.attributes.each do |attribute| %>
33
- <% next if attribute[:field].nil? %>
34
- <% next unless attribute[:field].visible?(action_name) %>
35
-
36
- <% field = attribute[:field] %>
37
-
38
- <td><%= render partial: field.to_partial_path("index"), locals: { field: field, record: record } %></td>
47
+ <% resource.attributes.values.each do |attribute| %>
48
+ <% next if attribute.field.nil? %>
49
+ <% next unless attribute.field.visible?(action_name) %>
50
+ <td class="px-4 py-2"><%= render partial: attribute.field.to_partial_path("index"), locals: { field: attribute.field, record: record } %></td>
39
51
  <% end %>
40
52
 
41
- <td><%= link_to "View", resource.show_path(record) %></td>
53
+ <td class="px-4 py-2 text-center"><%= link_to "View", resource.show_path(record), class: "text-indigo-500" %></td>
42
54
  </tr>
43
55
  <% end %>
44
56
  </tbody>