madmin 1.2.11 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +1 -4
  42. data/app/views/madmin/fields/enum/_form.html.erb +1 -4
  43. data/app/views/madmin/fields/file/_form.html.erb +1 -4
  44. data/app/views/madmin/fields/float/_form.html.erb +1 -4
  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 +1 -4
  48. data/app/views/madmin/fields/integer/_index.html.erb +1 -5
  49. data/app/views/madmin/fields/json/_form.html.erb +1 -4
  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 +1 -4
  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 +1 -4
  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 +1 -4
  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 } %>