madmin 1.2.10 → 2.0.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/madmin_manifest.js +3 -0
  3. data/app/assets/stylesheets/madmin/actiontext.css +31 -0
  4. data/app/assets/stylesheets/madmin/application.css +88 -0
  5. data/app/assets/stylesheets/madmin/buttons.css +46 -0
  6. data/app/assets/stylesheets/madmin/forms.css +82 -0
  7. data/app/assets/stylesheets/madmin/pagination.css +59 -0
  8. data/app/assets/stylesheets/madmin/reset.css +242 -0
  9. data/app/assets/stylesheets/madmin/sidebar.css +80 -0
  10. data/app/assets/stylesheets/madmin/tables.css +56 -0
  11. data/app/controllers/madmin/application_controller.rb +7 -12
  12. data/app/controllers/madmin/base_controller.rb +1 -0
  13. data/app/helpers/madmin/application_helper.rb +1 -12
  14. data/app/helpers/madmin/sort_helper.rb +17 -1
  15. data/app/javascript/madmin/application.js +4 -0
  16. data/app/javascript/madmin/controllers/application.js +12 -0
  17. data/app/javascript/madmin/controllers/index.js +5 -0
  18. data/app/javascript/madmin/controllers/nested_form_controller.js +34 -0
  19. data/app/javascript/madmin/controllers/select_controller.js +32 -0
  20. data/app/views/layouts/madmin/application.html.erb +11 -13
  21. data/app/views/madmin/application/_form.html.erb +13 -12
  22. data/app/views/madmin/application/_javascript.html.erb +3 -136
  23. data/app/views/madmin/application/_navigation.html.erb +22 -27
  24. data/app/views/madmin/application/edit.html.erb +9 -5
  25. data/app/views/madmin/application/index.html.erb +37 -31
  26. data/app/views/madmin/application/new.html.erb +9 -5
  27. data/app/views/madmin/application/show.html.erb +28 -22
  28. data/app/views/madmin/dashboard/show.html.erb +4 -1
  29. data/app/views/madmin/fields/attachment/_form.html.erb +11 -4
  30. data/app/views/madmin/fields/attachment/_index.html.erb +5 -1
  31. data/app/views/madmin/fields/attachment/_show.html.erb +4 -4
  32. data/app/views/madmin/fields/attachments/_form.html.erb +1 -4
  33. data/app/views/madmin/fields/belongs_to/_form.html.erb +1 -4
  34. data/app/views/madmin/fields/belongs_to/_index.html.erb +2 -1
  35. data/app/views/madmin/fields/boolean/_form.html.erb +3 -4
  36. data/app/views/madmin/fields/currency/_form.html.erb +1 -0
  37. data/app/views/madmin/fields/currency/_index.html.erb +1 -0
  38. data/app/views/madmin/fields/currency/_show.html.erb +1 -0
  39. data/app/views/madmin/fields/date/_form.html.erb +1 -4
  40. data/app/views/madmin/fields/date_time/_form.html.erb +1 -4
  41. data/app/views/madmin/fields/decimal/_form.html.erb +0 -3
  42. data/app/views/madmin/fields/enum/_form.html.erb +1 -4
  43. data/app/views/madmin/fields/file/_form.html.erb +0 -3
  44. data/app/views/madmin/fields/float/_form.html.erb +0 -3
  45. data/app/views/madmin/fields/has_many/_form.html.erb +1 -4
  46. data/app/views/madmin/fields/has_one/_form.html.erb +0 -3
  47. data/app/views/madmin/fields/integer/_form.html.erb +0 -3
  48. data/app/views/madmin/fields/integer/_index.html.erb +1 -5
  49. data/app/views/madmin/fields/json/_form.html.erb +0 -3
  50. data/app/views/madmin/fields/nested_has_many/_fields.html.erb +4 -5
  51. data/app/views/madmin/fields/nested_has_many/_form.html.erb +0 -4
  52. data/app/views/madmin/fields/password/_form.html.erb +0 -3
  53. data/app/views/madmin/fields/polymorphic/_form.html.erb +1 -4
  54. data/app/views/madmin/fields/rich_text/_form.html.erb +1 -6
  55. data/app/views/madmin/fields/select/_form.html.erb +1 -0
  56. data/app/views/madmin/fields/select/_index.html.erb +1 -0
  57. data/app/views/madmin/fields/select/_show.html.erb +1 -0
  58. data/app/views/madmin/fields/string/_form.html.erb +1 -4
  59. data/app/views/madmin/fields/text/_form.html.erb +0 -3
  60. data/app/views/madmin/shared/_label.html.erb +2 -2
  61. data/config/importmap.rb +10 -0
  62. data/lib/generators/madmin/field/templates/_form.html.erb +0 -3
  63. data/lib/generators/madmin/install/templates/controller.rb.tt +5 -12
  64. data/lib/generators/madmin/resource/templates/resource.rb.tt +12 -10
  65. data/lib/madmin/engine.rb +20 -0
  66. data/lib/madmin/field.rb +17 -5
  67. data/lib/madmin/fields/belongs_to.rb +10 -5
  68. data/lib/madmin/fields/currency.rb +15 -0
  69. data/lib/madmin/fields/polymorphic.rb +1 -1
  70. data/lib/madmin/fields/select.rb +9 -0
  71. data/lib/madmin/menu.rb +70 -0
  72. data/lib/madmin/resource.rb +56 -24
  73. data/lib/madmin/search.rb +1 -1
  74. data/lib/madmin/version.rb +1 -1
  75. data/lib/madmin.rb +22 -1
  76. metadata +61 -13
  77. data/app/assets/config/manifest.js +0 -2
  78. data/app/assets/stylesheets/actiontext.scss +0 -36
  79. data/app/assets/stylesheets/application.css +0 -15
  80. data/app/views/madmin/application/_menu_resources.html.erb +0 -7
@@ -1,22 +1,17 @@
1
1
  module Madmin
2
2
  class ApplicationController < Madmin::BaseController
3
+ include Rails.application.routes.url_helpers
4
+
3
5
  before_action :authenticate_admin_user
4
6
 
5
7
  def authenticate_admin_user
6
8
  # TODO: Add your authentication logic here
7
9
 
8
- # For example, we could redirect if the user isn't an admin
9
- # redirect_to "/", alert: "Not authorized." unless user_signed_in? && current_user.admin?
10
- end
11
-
12
- # Authenticate with Clearance
13
- # include Clearance::Controller
14
- # before_action :require_login
10
+ # For example, with Rails 8 authentication
11
+ # redirect_to "/", alert: "Not authorized." unless authenticated? && Current.user.admin?
15
12
 
16
- # Authenticate with Devise
17
- # before_action :authenticate_user!
18
-
19
- # Authenticate with Basic Auth
20
- # http_basic_authenticate_with(name: Rails.application.credentials.admin_username, password: Rails.application.credentials.admin_password)
13
+ # Or with Devise
14
+ # redirect_to "/", alert: "Not authorized." unless current_user&.admin?
15
+ end
21
16
  end
22
17
  end
@@ -1,5 +1,6 @@
1
1
  module Madmin
2
2
  class BaseController < ActionController::Base
3
+ include ::ActiveStorage::SetCurrent if defined?(::ActiveStorage)
3
4
  include Pagy::Backend
4
5
 
5
6
  protect_from_forgery with: :exception
@@ -1,18 +1,7 @@
1
1
  module Madmin
2
2
  module ApplicationHelper
3
3
  include Pagy::Frontend
4
-
5
- # Converts a Rails version to a NPM version
6
- def npm_rails_version
7
- version = [
8
- Rails::VERSION::MAJOR,
9
- Rails::VERSION::MINOR,
10
- Rails::VERSION::TINY
11
- ].join(".")
12
-
13
- version += "-#{Rails::VERSION::PRE}" if Rails::VERSION::PRE
14
- version
15
- end
4
+ include Rails.application.routes.url_helpers
16
5
 
17
6
  def clear_search_params
18
7
  resource.index_path(sort: params[:sort], direction: params[:direction])
@@ -8,7 +8,7 @@ module Madmin
8
8
  concat title
9
9
  if matching_column
10
10
  concat " "
11
- concat tag.i((sort_direction == "asc") ? "▲" : "▼")
11
+ concat tag.span((sort_direction == "asc") ? asc_icon : desc_icon)
12
12
  end
13
13
  end
14
14
  end
@@ -28,5 +28,21 @@ module Madmin
28
28
  def default_sort_direction
29
29
  resource.try(:default_sort_direction) || "desc"
30
30
  end
31
+
32
+ def asc_icon
33
+ <<~SVG.html_safe
34
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" height="1rem" width="1rem">
35
+ <path fill-rule="evenodd" d="M11.78 9.78a.75.75 0 0 1-1.06 0L8 7.06 5.28 9.78a.75.75 0 0 1-1.06-1.06l3.25-3.25a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06Z" clip-rule="evenodd" />
36
+ </svg>
37
+ SVG
38
+ end
39
+
40
+ def desc_icon
41
+ <<~SVG.html_safe
42
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" height="1rem" width="1rem">
43
+ <path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
44
+ </svg>
45
+ SVG
46
+ end
31
47
  end
32
48
  end
@@ -0,0 +1,4 @@
1
+ import "@hotwired/turbo-rails"
2
+ import "trix"
3
+ import "@rails/actiontext"
4
+ import "controllers"
@@ -0,0 +1,12 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }
10
+
11
+ import { Dropdown } from "tailwindcss-stimulus-components"
12
+ application.register("dropdown", Dropdown)
@@ -0,0 +1,5 @@
1
+ import { application } from "controllers/application"
2
+
3
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
4
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
5
+ eagerLoadControllersFrom("controllers", application)
@@ -0,0 +1,34 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static get targets() {
5
+ return [ "links", "template" ]
6
+ }
7
+
8
+ connect() {
9
+ this.wrapperClass = this.data.get("wrapperClass") || "nested-fields"
10
+ }
11
+
12
+ add_association(event) {
13
+ event.preventDefault()
14
+
15
+ var content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
16
+ this.linksTarget.insertAdjacentHTML('beforebegin', content)
17
+ }
18
+
19
+ remove_association(event) {
20
+ event.preventDefault()
21
+
22
+ let wrapper = event.target.closest("." + this.wrapperClass)
23
+
24
+ // New records are simply removed from the page
25
+ if (wrapper.dataset.newRecord == "true") {
26
+ wrapper.remove()
27
+
28
+ // Existing records are hidden and flagged for deletion
29
+ } else {
30
+ wrapper.querySelector("input[name*='_destroy']").value = 1
31
+ wrapper.style.display = 'none'
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,32 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import TomSelect from "tom-select"
3
+
4
+ export default class extends Controller {
5
+ static values = {
6
+ options: Object,
7
+ url: String
8
+ }
9
+
10
+ connect() {
11
+ this.select = new TomSelect(this.element, {
12
+ plugins: ['remove_button'],
13
+ valueField: 'id',
14
+ labelField: 'name',
15
+ searchField: 'name',
16
+ load: (search, callback) => {
17
+ let url = search ? `${this.urlValue}?q=${search}` : this.urlValue;
18
+ fetch(url)
19
+ .then(response => response.json())
20
+ .then(json => {
21
+ callback(json);
22
+ }).catch(() => {
23
+ callback();
24
+ });
25
+ }
26
+ })
27
+ }
28
+
29
+ disconnect() {
30
+ this.select.destroy()
31
+ }
32
+ }
@@ -5,22 +5,20 @@
5
5
  <meta name="ROBOTS" content="NOODP">
6
6
  <meta name="viewport" content="initial-scale=1">
7
7
  <title>
8
- Madmin: <%= Rails.application.class %>
8
+ <% if content_for? :title %>
9
+ <%= yield(:title) %> -
10
+ <% end %>
11
+ <%= Madmin.site_name %> Admin
9
12
  </title>
10
-
11
13
  <%= csrf_meta_tags %>
12
-
13
14
  <%= render "javascript" %>
14
15
  </head>
15
- <body class="min-h-screen">
16
- <div class="md:flex w-full min-h-screen">
17
- <div id="sidebar" class="md:w-64 p-4 flex-shrink-0 border-r">
18
- <%= render "navigation" -%>
19
- </div>
20
- <main class="flex-grow p-4 overflow-x-scroll" role="main">
21
- <%#= render "flashes" -%>
22
- <%= yield %>
23
- </main>
24
- </div>
16
+ <body>
17
+ <aside id="sidebar">
18
+ <%= render "navigation" %>
19
+ </aside>
20
+ <main>
21
+ <%= yield %>
22
+ </main>
25
23
  </body>
26
24
  </html>
@@ -1,23 +1,24 @@
1
1
  <%= form_with model: [:madmin, record], url: (record.persisted? ? resource.show_path(record) : resource.index_path), local: true do |form| %>
2
2
  <% if form.object.errors.any? %>
3
- <div class="mb-4 rounded-md text-sm text-red-700 bg-red-100 p-4">
4
- <div class="mb-2 font-medium leading-5 text-red-800">There was <%= pluralize form.object.errors.full_messages.count, "error" %> with your submission</div>
3
+ <div class="alert alert-danger">
4
+ <div class="">There was <%= pluralize form.object.errors.full_messages.count, "error" %> with your submission:</div>
5
5
 
6
- <% form.object.errors.full_messages.each do |message| %>
7
- <div class="ml-4"><%= message %></div>
8
- <% end %>
6
+ <ul>
7
+ <% form.object.errors.full_messages.each do |message| %>
8
+ <li><%= message %></li>
9
+ <% end %>
10
+ </ul>
9
11
  </div>
10
12
  <% end %>
11
13
 
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
-
17
- <div class="mb-4 md:flex">
14
+ <% resource.attributes.values.select{ _1.field.present? && _1.field.visible?(action_name) }.each do |attribute| %>
15
+ <div class="form-group">
16
+ <%= render "madmin/shared/label", form: form, field: attribute.field %>
18
17
  <%= render partial: attribute.field.to_partial_path("form"), locals: { field: attribute.field, record: record, form: form, resource: resource } %>
18
+ <%= tag.div attribute.field.options.description, class: "form-description" if attribute.field.options.description.present? %>
19
19
  </div>
20
20
  <% end %>
21
21
 
22
- <%= form.submit class: "bg-white hover:bg-gray-100 text-gray-800 font-semibold py-2 px-4 border border-gray-400 rounded shadow" %>
22
+ <%= form.submit class: "btn btn-primary" %>
23
+ <%= link_to "Cancel", (record.persisted? ? resource.show_path(record) : resource.index_path), class: "btn" %>
23
24
  <% end %>
@@ -1,138 +1,5 @@
1
- <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>
1
+ <%= javascript_importmap_tags "application", importmap: Madmin.importmap %>
2
+
3
+ <%= stylesheet_link_tag "madmin/application", "data-turbo-track": "reload" %>
2
4
  <%= stylesheet_link_tag "https://unpkg.com/flatpickr/dist/flatpickr.min.css", "data-turbo-track": "reload" %>
3
- <%= stylesheet_link_tag "https://unpkg.com/trix/dist/trix.css", "data-turbo-track": "reload" %>
4
5
  <%= stylesheet_link_tag "https://unpkg.com/tom-select/dist/css/tom-select.min.css", "data-turbo-track": "reload" %>
5
- <style type="text/tailwindcss">
6
- .pagy {
7
- @apply isolate inline-flex rounded-md;
8
-
9
- a:first-child {
10
- @apply rounded-l-md;
11
- }
12
- a:last-child {
13
- @apply rounded-r-md;
14
- }
15
-
16
- a {
17
- @apply relative -ml-px inline-flex items-center bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-100 focus:z-10;
18
-
19
- &:not([href]) {
20
- @apply text-gray-300 cursor-default;
21
- }
22
-
23
- &.current {
24
- @apply text-white bg-blue-500 ring-blue-500;
25
- }
26
- }
27
-
28
- label {
29
- @apply inline-block whitespace-nowrap bg-gray-200 rounded-lg px-3 py-0.5;
30
- input {
31
- @apply bg-gray-100 border-none rounded-md;
32
- }
33
- }
34
- }
35
- </style>
36
-
37
- <script type="importmap" data-turbo-track="reload">
38
- {
39
- "imports": {
40
- "@hotwired/stimulus": "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js",
41
- "@hotwired/turbo": "https://unpkg.com/@hotwired/turbo",
42
- "@hotwired/turbo-rails": "https://unpkg.com/@hotwired/turbo-rails",
43
- "@rails/actiontext": "https://unpkg.com/@rails/actiontext@<%= npm_rails_version %>/app/assets/javascripts/actiontext.js",
44
- "@rails/activestorage": "https://unpkg.com/@rails/activestorage@<%= npm_rails_version %>/app/assets/javascripts/activestorage.esm.js",
45
- "flatpickr": "https://unpkg.com/flatpickr/dist/esm/index.js",
46
- "stimulus-flatpickr": "https://unpkg.com/stimulus-flatpickr@3.0.0-0/dist/index.m.js",
47
- "tailwindcss-stimulus-components": "https://unpkg.com/tailwindcss-stimulus-components/dist/tailwindcss-stimulus-components.module.js",
48
- "tom-select": "https://unpkg.com/tom-select/dist/esm/tom-select.complete.js",
49
- "trix": "https://unpkg.com/trix"
50
- }
51
- }
52
- </script>
53
- <script async src="https://unpkg.com/es-module-shims/dist/es-module-shims.js"></script>
54
-
55
- <script type="module">
56
- import * as Turbo from "@hotwired/turbo-rails"
57
-
58
- import * as ActiveStorage from "@rails/activestorage"
59
- ActiveStorage.start()
60
- import "trix"
61
- import "@rails/actiontext"
62
-
63
- import { Application, Controller } from '@hotwired/stimulus'
64
- const application = Application.start()
65
-
66
- import { Dropdown } from "tailwindcss-stimulus-components"
67
- application.register("dropdown", Dropdown)
68
-
69
- import StimulusFlatpickr from "stimulus-flatpickr"
70
- application.register("flatpickr", StimulusFlatpickr)
71
-
72
- import TomSelect from "tom-select"
73
-
74
- (() => {
75
- application.register('select', class extends Controller {
76
- static values = {
77
- options: Object,
78
- url: String
79
- }
80
-
81
- connect() {
82
- this.select = new TomSelect(this.element, {
83
- plugins: ['remove_button'],
84
- valueField: 'id',
85
- labelField: 'name',
86
- searchField: 'name',
87
- load: (search, callback) => {
88
- let url = search ? `${this.urlValue}?q=${search}` : this.urlValue;
89
- fetch(url)
90
- .then(response => response.json())
91
- .then(json => {
92
- callback(json);
93
- }).catch(() => {
94
- callback();
95
- });
96
- }
97
- })
98
- }
99
-
100
- disconnect() {
101
- this.select.destroy()
102
- }
103
- })
104
-
105
- application.register('nested-form', class extends Controller {
106
- static get targets() {
107
- return [ "links", "template" ]
108
- }
109
-
110
- connect() {
111
- this.wrapperClass = this.data.get("wrapperClass") || "nested-fields"
112
- }
113
-
114
- add_association(event) {
115
- event.preventDefault()
116
-
117
- var content = this.templateTarget.innerHTML.replace(/NEW_RECORD/g, new Date().getTime())
118
- this.linksTarget.insertAdjacentHTML('beforebegin', content)
119
- }
120
-
121
- remove_association(event) {
122
- event.preventDefault()
123
-
124
- let wrapper = event.target.closest("." + this.wrapperClass)
125
-
126
- // New records are simply removed from the page
127
- if (wrapper.dataset.newRecord == "true") {
128
- wrapper.remove()
129
-
130
- // Existing records are hidden and flagged for deletion
131
- } else {
132
- wrapper.querySelector("input[name*='_destroy']").value = 1
133
- wrapper.style.display = 'none'
134
- }
135
- }
136
- })
137
- })()
138
- </script>
@@ -1,29 +1,24 @@
1
- <div class="flex flex-col h-full text-sm">
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>
1
+ <h1><%= link_to_if respond_to?(:root_url), Madmin.site_name, root_url, data: {turbo: false} %></h1>
11
2
 
12
- <div class="-mr-2 flex items-center md:hidden relative" data-controller="dropdown">
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>
3
+ <nav>
4
+ <%= nav_link_to "Dashboard", madmin_root_path %>
20
5
 
21
- <div class="absolute top-12 right-0 bg-white rounded-lg min-w-64 shadow-lg hidden md:flex flex-col flex-grow justify-between" data-dropdown-target="menu">
22
- <%= render "menu_resources" %>
23
- </div>
24
- </div>
25
- <div class="hidden md:block">
26
- <%= render "menu_resources" %>
27
- </div>
28
- </div>
29
- </div>
6
+ <% Madmin.menu.render do |item| %>
7
+ <% if item.url %>
8
+ <%= nav_link_to item.label, item.url, starts_with: item.url %>
9
+ <% else %>
10
+ <h4><%= item.label %></h4>
11
+ <% end %>
12
+
13
+ <% item.items.each do |item| %>
14
+ <%= nav_link_to item.label, item.url, starts_with: item.url %>
15
+ <% end %>
16
+ <% end %>
17
+ </nav>
18
+
19
+ <footer>
20
+ <%= link_to "https://github.com/excid3/madmin", target: :_blank do %>
21
+ <svg viewBox="0 0 16 16" height="1rem" width="1rem" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
22
+ Madmin on GitHub
23
+ <% end %>
24
+ </footer>
@@ -1,7 +1,11 @@
1
- <h1 class="text-xl mb-4">
2
- <%= link_to resource.friendly_name.pluralize, resource.index_path, class: "text-blue-500" %>
3
- /
4
- <strong>Edit <%= link_to resource.display_name(@record), resource.show_path(@record), class: "text-blue-500" %></strong>
5
- </h1>
1
+ <%= content_for :title, "New #{resource.display_name(@record)}" %>
2
+
3
+ <header class="header">
4
+ <h1>
5
+ <%= link_to resource.friendly_name.pluralize, resource.index_path %>
6
+ /
7
+ <strong>Edit <%= link_to resource.display_name(@record), resource.show_path(@record) %></strong>
8
+ </h1>
9
+ </header>
6
10
 
7
11
  <%= render partial: "form", locals: { record: @record, resource: resource } %>
@@ -1,62 +1,68 @@
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-blue-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 %>
1
+ <%= content_for :title, resource.friendly_name.pluralize %>
2
+
3
+ <header class="header">
4
+ <h1><%= resource.friendly_name.pluralize %></h1>
5
+
6
+ <div class="actions">
7
+ <form class="search">
8
+ <%= hidden_field_tag :page, params[:page], value: 1 %>
9
+ <%= search_field_tag :q, params[:q], placeholder: "Search" %>
13
10
  </form>
14
11
 
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 %>
12
+ <%= link_to clear_search_params, class: "btn btn-secondary" do %>
13
+ <svg xmlns="http://www.w3.org/2000/svg" height="1rem" width="1rem" viewBox="0 0 20 20" fill="currentColor">
14
+ <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" />
15
+ </svg>
16
+ <% end if params[:q].present? %>
17
+
18
+ <%= link_to "New #{resource.friendly_name}", resource.new_path, class: "btn btn-secondary" %>
18
19
  </div>
19
- </div>
20
+ </header>
20
21
 
21
- <div class="mb-4">
22
+ <nav class="scopes">
22
23
  <% if resource.scopes.any? %>
23
- <%= link_to "All", resource.index_path, class: class_names("p-2 rounded", {"bg-gray-100" => params[:scope].blank?}) %>
24
+ <%= link_to "All", resource.index_path, class: class_names("btn btn-secondary", {"active" => params[:scope].blank?}) %>
24
25
  <% end %>
25
26
 
26
27
  <% resource.scopes.each do |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}) %>
28
+ <%= link_to scope.to_s.humanize, resource.index_path(scope: scope), class: class_names("btn btn-secondary", {"active" => params[:scope] == scope.to_s}) %>
28
29
  <% end %>
29
- </div>
30
- <div class="min-w-full max-w-xl overflow-x-auto pb-4">
31
- <table class="min-w-full divide-y divide-gray-200">
30
+ </nav>
31
+
32
+ <div class="table-scroll">
33
+ <table>
32
34
  <thead>
33
- <tr class="border-b border-gray-200">
35
+ <tr>
34
36
  <% resource.attributes.values.each do |attribute| %>
35
37
  <% next if attribute.field.nil? %>
36
38
  <% next unless attribute.field.visible?(action_name) %>
37
39
 
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>
40
+ <th><%= sortable attribute.name, attribute.name.to_s.titleize %></th>
39
41
  <% end %>
40
- <th class="py-2 px-4 text-left text-xs text-gray-500 font-medium uppercase">Actions</th>
42
+ <th></th>
41
43
  </tr>
42
44
  </thead>
43
45
 
44
- <tbody class="text-sm divide-y">
46
+ <tbody>
45
47
  <% @records.each do |record| %>
46
48
  <tr>
47
49
  <% resource.attributes.values.each do |attribute| %>
48
50
  <% next if attribute.field.nil? %>
49
51
  <% 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, resource: resource } %></td>
52
+ <td><%= render partial: attribute.field.to_partial_path("index"), locals: { field: attribute.field, record: record, resource: resource } %></td>
51
53
  <% end %>
52
54
 
53
- <td class="px-4 py-2 text-center">
54
- <%= link_to "View", resource.show_path(record), class: "text-blue-500" %>
55
- <%= link_to "Edit", resource.edit_path(record), class: "text-blue-500" %>
55
+ <td>
56
+ <%= link_to "View", resource.show_path(record) %>
57
+ <%= link_to "Edit", resource.edit_path(record) %>
56
58
  </td>
57
59
  </tr>
58
60
  <% end %>
59
61
  </tbody>
60
62
  </table>
61
63
  </div>
62
- <%== pagy_nav @pagy %>
64
+
65
+ <div class="pagination">
66
+ <%== pagy_nav @pagy if @pagy.pages > 1 %>
67
+ <span>Showing <%= tag.strong @pagy.in %> of <%= tag.strong @pagy.count %></span>
68
+ </div>
@@ -1,7 +1,11 @@
1
- <h1 class="text-xl mb-4">
2
- <%= link_to resource.friendly_name.pluralize, resource.index_path, class: "text-blue-500" %>
3
- /
4
- <strong>New <%= resource.friendly_name %></strong>
5
- </h1>
1
+ <%= content_for :title, "New #{resource.friendly_name}" %>
2
+
3
+ <header class="header">
4
+ <h1>
5
+ <%= link_to resource.friendly_name.pluralize, resource.index_path %>
6
+ /
7
+ <strong>New <%= resource.friendly_name %></strong>
8
+ </h1>
9
+ </header>
6
10
 
7
11
  <%= render partial: "form", locals: { record: @record, resource: resource } %>