js_admin 0.0.1 → 0.2.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +227 -19
  4. data/app/assets/javascripts/js_admin/application.js +13 -0
  5. data/app/assets/javascripts/js_admin/controllers/flash_controller.js +23 -0
  6. data/app/assets/javascripts/js_admin/controllers/theme_controller.js +62 -0
  7. data/app/assets/tailwind/js_admin/engine.css +4 -0
  8. data/app/controllers/js_admin/application_controller.rb +45 -1
  9. data/app/controllers/js_admin/home_controller.rb +6 -0
  10. data/app/controllers/js_admin/resources_controller.rb +21 -6
  11. data/app/helpers/js_admin/application_helper.rb +292 -19
  12. data/app/views/js_admin/home/index.html.erb +5 -0
  13. data/app/views/js_admin/models/_index_content.html.erb +20 -0
  14. data/app/views/js_admin/models/index.html.erb +4 -21
  15. data/app/views/js_admin/resources/_edit_content.html.erb +11 -0
  16. data/app/views/js_admin/resources/_form.html.erb +21 -13
  17. data/app/views/js_admin/resources/_index_content.html.erb +74 -0
  18. data/app/views/js_admin/resources/_new_content.html.erb +10 -0
  19. data/app/views/js_admin/resources/_show_content.html.erb +26 -0
  20. data/app/views/js_admin/resources/destroy.turbo_stream.erb +7 -0
  21. data/app/views/js_admin/resources/edit.html.erb +4 -12
  22. data/app/views/js_admin/resources/index.html.erb +3 -52
  23. data/app/views/js_admin/resources/new.html.erb +4 -11
  24. data/app/views/js_admin/resources/show.html.erb +3 -26
  25. data/app/views/layouts/js_admin/_content_frame.html.erb +7 -0
  26. data/app/views/layouts/js_admin/_flash.html.erb +3 -0
  27. data/app/views/layouts/js_admin/_flash_messages.html.erb +9 -0
  28. data/app/views/layouts/js_admin/_sidebar.html.erb +36 -0
  29. data/app/views/layouts/js_admin/application.html.erb +19 -17
  30. data/config/locales/js_admin.en.yml +65 -0
  31. data/config/locales/js_admin.ko.yml +65 -0
  32. data/config/routes.rb +2 -1
  33. data/lib/generators/js_admin/install/install_generator.rb +70 -5
  34. data/lib/js_admin/configuration.rb +82 -3
  35. data/lib/js_admin/configuration_error.rb +4 -0
  36. data/lib/js_admin/field.rb +21 -7
  37. data/lib/js_admin/registry.rb +4 -1
  38. data/lib/js_admin/resource.rb +17 -5
  39. data/lib/js_admin/version.rb +1 -1
  40. data/lib/js_admin.rb +8 -0
  41. metadata +40 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b816de43bee7878c7eccf7acb2a158b304cbfbbb80bd65566329303fea76c0e2
4
- data.tar.gz: 5c760edf388ea5dfc66985197ca48f99d79cfd4af9923635c98e4d93217856fc
3
+ metadata.gz: bacbc059bd6ee6d36d9ad35c5fa86b8cb258b4d1fda939bf3bd98b269f74c7ea
4
+ data.tar.gz: ef27365d7d4d61abfb48a114bcd9a1eb3a5f569b943a2369951f12e4e78aef22
5
5
  SHA512:
6
- metadata.gz: 753fd6b7de19253fc292b4f32654231cda74211c131a07080128cdd3290b9acd8229b3c7f320c5bb32a7248113bff3b04062cf4bbcfc1d4b5d39bcbbd6de57a3
7
- data.tar.gz: 1b26bb7126e1404f1bd0d85f8ab6693eee2b94c029e9c0e4c0a5775d7b921af3e0d68407b22ba7c3e7c41fbb17e63852f6b83a43a9bc24972f46798a906279b0
6
+ metadata.gz: bccfe7a4bc553ff4581f6a31a4beb244fe47a39cec17a3d851b527ff74f8d2b3d8d12aa0e3f71a85ac74b2bbcd4bccddb919327cb8235c4688fa37483cc91973
7
+ data.tar.gz: 79370d4f7c372a9527924c0768f641109424b24c55fb42ef2f16011702f518b04560201c5b3c43c2200c860a60a447f1edf72ce542f33da2d53a3ab2af5a74aa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.0 - 2026-10-18
6
+
7
+ - Add host application authentication and authorization callbacks as an alternative to Basic Auth.
8
+ - Reject conflicting or incomplete authentication configuration with localized errors.
9
+ - Add English and Korean I18n translations for the JSAdmin interface.
10
+ - Add `select_options` configuration to render a column as a select with static or callable choices.
11
+
12
+ ## 0.1.0 - 2026-07-18
13
+
14
+ - Change the default mount path to `/admin`.
15
+ - Add optional HTTP Basic Auth configuration.
16
+ - Generate a commented `config/initializers/js_admin.rb` during installation.
17
+ - Pin JSAdmin JavaScript through `importmap-rails` during installation.
18
+ - Add sidebar model navigation with namespace grouping.
19
+ - Use Turbo Frames, Turbo Streams, and Stimulus controllers for the admin shell.
20
+ - Add fixed header/sidebar layout, dark mode support, and improved Tailwind UI spacing.
21
+ - Add compact pagination controls and set the default page size to 10 records.
22
+ - Add `View`, `Edit`, and `Delete` actions to resource tables.
23
+ - Render form validation errors inline without Rails `field_with_errors` layout wrappers.
24
+ - Add sandbox setup for browser testing with richer demo models and seed data.
25
+ - Add AGENTS/CLAUDE project guidance files for LLM-assisted development.
26
+
3
27
  ## 0.0.1 - 2026-07-12
4
28
 
5
29
  - Initial release for RubyGems publishing test.
data/README.md CHANGED
@@ -1,19 +1,20 @@
1
1
  # JSAdmin
2
2
 
3
- JSAdmin is a mountable Rails engine that exposes Active Record models through
4
- a generic CRUD UI. It discovers models from the host Rails application and builds
3
+ JSAdmin is a mountable Rails engine that exposes Active Record models through a
4
+ generic CRUD UI. It discovers models from the host Rails application and builds
5
5
  forms from database column metadata.
6
6
 
7
- > 2026-07-12 기준 RubyGems API에서 `js_admin`은 등록되지 않은 이름으로 확인했습니다. 공개 배포 직전에는 다시 한 번 점유 여부를 확인하세요.
7
+ It is built for Rails 8.1+, Tailwind CSS, and Hotwire-compatible Rails helpers.
8
8
 
9
9
  ## Installation
10
+
10
11
  Add this line to your application's Gemfile:
11
12
 
12
13
  ```ruby
13
- gem "js_admin", path: "../js_admin"
14
+ gem "js_admin"
14
15
  ```
15
16
 
16
- And then execute:
17
+ Install and mount the engine:
17
18
 
18
19
  ```bash
19
20
  bundle install
@@ -21,31 +22,138 @@ bin/rails generate js_admin:install
21
22
  bin/rails tailwindcss:engines
22
23
  ```
23
24
 
24
- The install generator mounts the engine at `/js_admin` and imports the engine
25
- Tailwind entrypoint when `app/assets/tailwind/application.css` exists.
25
+ The install generator mounts JSAdmin at `/admin`, pins JSAdmin's JavaScript in
26
+ `config/importmap.rb`, and imports the engine Tailwind entrypoint when
27
+ `app/assets/tailwind/application.css` exists. You can choose another mount path:
28
+
29
+ ```bash
30
+ bin/rails generate js_admin:install --path=/backoffice
31
+ bin/rails tailwindcss:engines
32
+ ```
33
+
34
+ JSAdmin uses `importmap-rails` for JavaScript loading. Rails 8 default apps
35
+ already include `config/importmap.rb`; if it was removed, run
36
+ `bin/rails importmap:install` before running `js_admin:install`.
37
+
38
+ The generator adds these pins when they are missing:
39
+
40
+ ```ruby
41
+ pin "@hotwired/turbo-rails", to: "turbo.min.js"
42
+ pin "@hotwired/stimulus", to: "stimulus.min.js"
43
+ pin "js_admin", to: "js_admin/application.js", preload: true
44
+ pin "js_admin/controllers/theme_controller", to: "js_admin/controllers/theme_controller.js", preload: true
45
+ pin "js_admin/controllers/flash_controller", to: "js_admin/controllers/flash_controller.js", preload: true
46
+ ```
26
47
 
27
48
  ## Usage
28
49
 
29
- Open:
50
+ Open the mounted path:
30
51
 
31
52
  ```text
32
- /js_admin
53
+ /admin
33
54
  ```
34
55
 
35
- By default, JSAdmin lists every concrete Active Record model with an existing
36
- database table. No per-model field setup is required.
56
+ The home page is intentionally empty. Model navigation lives in the sidebar.
57
+ By default, JSAdmin exposes every concrete Active Record model with an existing
58
+ database table. Namespaced models are grouped under namespace dropdowns. No
59
+ per-model field setup is required.
60
+
61
+ The UI includes a fixed header, fixed desktop sidebar, dark mode toggle, compact
62
+ pagination controls, and inline form validation errors. The default page size is
63
+ 10 records.
37
64
 
38
- Optional model filtering:
65
+ The install generator also creates `config/initializers/js_admin.rb` with
66
+ commented configuration examples. JSAdmin works without changing that file.
67
+
68
+ Available configuration options:
69
+
70
+ | Option | Default | Description |
71
+ | --- | --- | --- |
72
+ | `included_models` | `[]` | Model names/classes to expose. Empty means every discovered concrete Active Record model. |
73
+ | `excluded_models` | `[]` | Model names/classes to hide after discovery. |
74
+ | `records_per_page` | `10` | Number of records shown per resource index page. |
75
+ | `association_limit` | `100` | Maximum number of associated records loaded into a `belongs_to` select. |
76
+ | `display_name_methods` | `%i[name title email username id]` | Methods tried in order when displaying records in tables and select labels. |
77
+ | `field_select_options` | `{}` | Select choices by model and column. Register them with `select_options`. |
78
+ | `basic_auth_username` | `nil` | HTTP Basic Auth username. Auth is disabled unless username and password are both present. |
79
+ | `basic_auth_password` | `nil` | HTTP Basic Auth password. |
80
+ | `basic_auth_realm` | `"JSAdmin"` | HTTP Basic Auth realm. |
81
+ | `authenticate_with` | `nil` | Controller-context callback delegated to the host application's authentication. |
82
+ | `authorize_with` | `nil` | Optional controller-context callback that returns whether the authenticated user may access JSAdmin. |
83
+
84
+ Example:
39
85
 
40
86
  ```ruby
41
87
  # config/initializers/js_admin.rb
42
88
  JSAdmin.configure do |config|
43
89
  config.include_models User, Post
44
90
  config.exclude_models AuditLog
45
- config.records_per_page = 50
91
+ config.records_per_page = 10
92
+ config.select_options "Post", category: -> { Category.pluck(:name) }
93
+ end
94
+ ```
95
+
96
+ `select_options` renders the column as a select. Pass an array or a callable;
97
+ callables are evaluated each time the form renders. Choices can be values or
98
+ `[label, value]` pairs.
99
+
100
+ ## Authentication
101
+
102
+ JSAdmin works without authentication unless Basic Auth credentials or an
103
+ authentication callback are configured. Choose exactly one of the following
104
+ authentication strategies.
105
+
106
+ For HTTP Basic Auth, configure both username and password:
107
+
108
+ ```ruby
109
+ # config/initializers/js_admin.rb
110
+ JSAdmin.configure do |config|
111
+ config.basic_auth_username = ENV["JS_ADMIN_BASIC_AUTH_USERNAME"]
112
+ config.basic_auth_password = ENV["JS_ADMIN_BASIC_AUTH_PASSWORD"]
113
+ config.basic_auth_realm = "JSAdmin"
114
+ end
115
+ ```
116
+
117
+ To delegate authentication to the host application, provide a callback instead.
118
+ JSAdmin controllers inherit from the host application's `ApplicationController`,
119
+ and the callback runs in the controller context:
120
+
121
+ ```ruby
122
+ # config/initializers/js_admin.rb
123
+ JSAdmin.configure do |config|
124
+ config.authenticate_with do
125
+ authenticate_user!
126
+ end
127
+
128
+ config.authorize_with do
129
+ current_user&.admin?
130
+ end
46
131
  end
47
132
  ```
48
133
 
134
+ `authorize_with` is optional. When configured, returning `false` responds with
135
+ `403 Forbidden`. Basic Auth and `authenticate_with` cannot be configured
136
+ together. Partial Basic Auth credentials and `authorize_with` without
137
+ `authenticate_with` also raise `JSAdmin::ConfigurationError`.
138
+
139
+ For production apps, you can alternatively mount JSAdmin inside the host
140
+ application's admin authentication constraint.
141
+
142
+ ## Internationalization
143
+
144
+ JSAdmin follows the host application's `I18n.locale`.
145
+
146
+ Supported locales:
147
+
148
+ | Locale | Language |
149
+ | --- | --- |
150
+ | `en` | English |
151
+ | `ko` | 한국어 |
152
+
153
+ The translations cover the interface, CRUD notices, configuration errors, and
154
+ generator messages. Host applications can override any key under the
155
+ `js_admin` namespace.
156
+
49
157
  ## Field Mapping
50
158
 
51
159
  JSAdmin maps database columns to inputs automatically:
@@ -56,6 +164,7 @@ JSAdmin maps database columns to inputs automatically:
56
164
  | `boolean` | checkbox |
57
165
  | `integer`, `bigint`, `float`, `decimal` | number input |
58
166
  | `date`, `datetime`, `time` | matching date/time input |
167
+ | `select_options` configured column | select |
59
168
  | Rails enum | select |
60
169
  | `belongs_to` foreign key | select |
61
170
  | other columns | text input |
@@ -63,14 +172,113 @@ JSAdmin maps database columns to inputs automatically:
63
172
  Primary keys, timestamps, STI inheritance columns, readonly attributes, and
64
173
  generated columns are not editable.
65
174
 
66
- ## Security
175
+ ## Hotwire Scope
176
+
177
+ JSAdmin depends on `importmap-rails`, `turbo-rails`, and `stimulus-rails` so it
178
+ works naturally in modern Rails apps using the Rails default Hotwire setup.
67
179
 
68
- Zero model configuration is convenient, but it is dangerous to expose without
69
- authentication. In production, mount the engine inside your app's admin
70
- authentication constraint.
180
+ The admin shell keeps the header and sidebar stable while the main CRUD area is
181
+ wrapped in a Turbo Frame. Sidebar navigation, resource links, forms, and
182
+ pagination update that frame instead of replacing the whole page. Delete actions
183
+ can respond with Turbo Streams to refresh the flash region and resource list.
71
184
 
72
- ## Contributing
73
- Contribution directions go here.
185
+ Stimulus controllers manage the dark mode toggle and auto-dismiss flash
186
+ messages. The selected theme is persisted in storage and a cookie so the engine
187
+ layout can render the initial dark class without inline JavaScript.
188
+
189
+ ## Validation Errors
190
+
191
+ Form validation errors are rendered inside the form next to the matching field.
192
+ JSAdmin renders form controls directly so Rails' default `field_with_errors`
193
+ wrapper does not break the Tailwind grid layout. Errors that do not belong to an
194
+ editable field are shown at the top of the form.
195
+
196
+ ## Development
197
+
198
+ Run the test suite:
199
+
200
+ ```bash
201
+ bundle install
202
+ bundle exec rails test
203
+ ```
204
+
205
+ Build the gem locally:
206
+
207
+ ```bash
208
+ gem build js_admin.gemspec
209
+ ```
210
+
211
+ ### Dummy App
212
+
213
+ This repository includes `test/dummy` as the Rails engine test app. It is used by
214
+ the test suite and should stay small.
215
+
216
+ You can run it manually for functional checks:
217
+
218
+ ```bash
219
+ bin/rails db:prepare
220
+ bin/rails server
221
+ ```
222
+
223
+ Open:
224
+
225
+ ```text
226
+ http://localhost:3000/admin
227
+ ```
228
+
229
+ ### Sandbox App
230
+
231
+ Use `sandbox/` when you want to verify the real installation flow in a fresh
232
+ Rails app. The sandbox directory is ignored by Git.
233
+
234
+ Create a sandbox app wired to this local checkout:
235
+
236
+ ```bash
237
+ bin/setup_sandbox
238
+ ```
239
+
240
+ The setup script creates several demo models, including a namespaced model, seeds
241
+ sample records, and runs an initial Tailwind build.
242
+
243
+ Run a one-off browser check:
244
+
245
+ ```bash
246
+ cd sandbox
247
+ bin/rails server
248
+ ```
249
+
250
+ For iterative UI/CSS development, run the Rails server and Tailwind watcher
251
+ together:
252
+
253
+ ```bash
254
+ cd sandbox
255
+ bin/dev
256
+ ```
257
+
258
+ Open:
259
+
260
+ ```text
261
+ http://localhost:3000/admin
262
+ ```
263
+
264
+ Rebuild it from scratch:
265
+
266
+ ```bash
267
+ rm -rf sandbox
268
+ bin/setup_sandbox
269
+ ```
270
+
271
+ ## Release
272
+
273
+ Releases are published to RubyGems through GitHub Actions and RubyGems Trusted
274
+ Publishing. To release a new version, update `lib/js_admin/version.rb`, update
275
+ `CHANGELOG.md`, and push a matching tag:
276
+
277
+ ```bash
278
+ git tag v0.0.2
279
+ git push origin v0.0.2
280
+ ```
74
281
 
75
282
  ## License
283
+
76
284
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,13 @@
1
+ import "@hotwired/turbo-rails"
2
+ import { Application } from "@hotwired/stimulus"
3
+ import ThemeController from "js_admin/controllers/theme_controller"
4
+ import FlashController from "js_admin/controllers/flash_controller"
5
+
6
+ const application = window.Stimulus || window.JSAdminStimulus || Application.start()
7
+
8
+ if (!window.Stimulus) {
9
+ window.JSAdminStimulus = application
10
+ }
11
+
12
+ application.register("js-admin-theme", ThemeController)
13
+ application.register("js-admin-flash", FlashController)
@@ -0,0 +1,23 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ timeout: { type: Number, default: 4000 }
6
+ }
7
+
8
+ connect() {
9
+ this.timeoutId = window.setTimeout(() => this.dismiss(), this.timeoutValue)
10
+ }
11
+
12
+ disconnect() {
13
+ window.clearTimeout(this.timeoutId)
14
+ }
15
+
16
+ dismiss() {
17
+ this.element.classList.add("opacity-0", "translate-y-1")
18
+
19
+ window.setTimeout(() => {
20
+ this.element.remove()
21
+ }, 300)
22
+ }
23
+ }
@@ -0,0 +1,62 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ const THEME_STORAGE_KEY = "js_admin_theme"
4
+ const THEME_COOKIE_NAME = "js_admin_theme"
5
+ const THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365
6
+
7
+ export default class extends Controller {
8
+ static targets = ["toggle"]
9
+
10
+ connect() {
11
+ this.applyPreferredTheme()
12
+ this.syncToggle()
13
+ }
14
+
15
+ toggle() {
16
+ const enabled = !document.documentElement.classList.contains("dark")
17
+
18
+ document.documentElement.classList.toggle("dark", enabled)
19
+ this.persistTheme(enabled)
20
+ this.syncToggle()
21
+ }
22
+
23
+ syncToggle() {
24
+ if (!this.hasToggleTarget) return
25
+
26
+ this.toggleTarget.setAttribute("aria-pressed", document.documentElement.classList.contains("dark"))
27
+ }
28
+
29
+ persistTheme(enabled) {
30
+ const theme = enabled ? "dark" : "light"
31
+
32
+ try {
33
+ localStorage.setItem(THEME_STORAGE_KEY, theme)
34
+ } catch (_error) {
35
+ }
36
+
37
+ document.cookie = `${THEME_COOKIE_NAME}=${theme}; path=/; max-age=${THEME_COOKIE_MAX_AGE}; SameSite=Lax`
38
+ }
39
+
40
+ applyPreferredTheme() {
41
+ const storedTheme = this.storedTheme()
42
+ const darkEnabled = storedTheme === "dark" || (!storedTheme && window.matchMedia("(prefers-color-scheme: dark)").matches)
43
+
44
+ document.documentElement.classList.toggle("dark", darkEnabled)
45
+ }
46
+
47
+ storedTheme() {
48
+ try {
49
+ return localStorage.getItem(THEME_STORAGE_KEY) || this.cookieTheme()
50
+ } catch (_error) {
51
+ return this.cookieTheme()
52
+ }
53
+ }
54
+
55
+ cookieTheme() {
56
+ const themeCookie = document.cookie
57
+ .split("; ")
58
+ .find((cookie) => cookie.startsWith(`${THEME_COOKIE_NAME}=`))
59
+
60
+ return themeCookie?.split("=")[1]
61
+ }
62
+ }
@@ -1,4 +1,8 @@
1
1
  @import "tailwindcss";
2
2
 
3
+ @custom-variant dark (&:where(.dark, .dark *));
4
+
5
+ @source "../../../views/layouts/js_admin/**/*.erb";
3
6
  @source "../../../views/js_admin/**/*.erb";
4
7
  @source "../../../helpers/js_admin/**/*.rb";
8
+ @source "../../../assets/javascripts/js_admin/**/*.js";
@@ -1,4 +1,48 @@
1
+ require "digest"
2
+
1
3
  module JSAdmin
2
- class ApplicationController < ActionController::Base
4
+ class ApplicationController < ::ApplicationController
5
+ before_action :authenticate_js_admin
6
+ before_action :authorize_js_admin
7
+
8
+ private
9
+
10
+ def authenticate_js_admin
11
+ case JSAdmin.configuration.authentication_strategy
12
+ when :basic_auth
13
+ authenticate_basic_auth
14
+ when :callback
15
+ instance_exec(&JSAdmin.configuration.authentication_callback)
16
+ end
17
+ end
18
+
19
+ def authenticate_basic_auth
20
+ authenticate_or_request_with_http_basic(basic_auth_realm) do |username, password|
21
+ secure_compare(username, JSAdmin.configuration.basic_auth_username) &&
22
+ secure_compare(password, JSAdmin.configuration.basic_auth_password)
23
+ end
24
+ end
25
+
26
+ def authorize_js_admin
27
+ callback = JSAdmin.configuration.authorization_callback
28
+ return unless callback
29
+ return if instance_exec(&callback)
30
+
31
+ head :forbidden
32
+ end
33
+
34
+ def basic_auth_realm
35
+ realm = JSAdmin.configuration.basic_auth_realm.to_s
36
+ return "JSAdmin" if realm.empty?
37
+
38
+ realm
39
+ end
40
+
41
+ def secure_compare(provided_value, expected_value)
42
+ ActiveSupport::SecurityUtils.secure_compare(
43
+ Digest::SHA256.hexdigest(provided_value.to_s),
44
+ Digest::SHA256.hexdigest(expected_value.to_s)
45
+ )
46
+ end
3
47
  end
4
48
  end
@@ -0,0 +1,6 @@
1
+ module JSAdmin
2
+ class HomeController < ApplicationController
3
+ def index
4
+ end
5
+ end
6
+ end
@@ -4,9 +4,7 @@ module JSAdmin
4
4
  before_action :set_record, only: %i[show edit update destroy]
5
5
 
6
6
  def index
7
- @page = [ params.fetch(:page, 1).to_i, 1 ].max
8
- @records = @resource.records(page: @page)
9
- @total_count = @resource.count
7
+ load_records
10
8
  end
11
9
 
12
10
  def show
@@ -20,7 +18,8 @@ module JSAdmin
20
18
  @record = @resource.new_record(resource_params)
21
19
 
22
20
  if @record.save
23
- redirect_to resource_path(@resource.model_id, @record), notice: "#{@resource.singular_label} was created."
21
+ redirect_to resource_path(@resource.model_id, @record),
22
+ notice: t("js_admin.resources.notices.created", resource: @resource.singular_label)
24
23
  else
25
24
  render :new, status: :unprocessable_entity
26
25
  end
@@ -31,7 +30,8 @@ module JSAdmin
31
30
 
32
31
  def update
33
32
  if @record.update(resource_params)
34
- redirect_to resource_path(@resource.model_id, @record), notice: "#{@resource.singular_label} was updated."
33
+ redirect_to resource_path(@resource.model_id, @record),
34
+ notice: t("js_admin.resources.notices.updated", resource: @resource.singular_label)
35
35
  else
36
36
  render :edit, status: :unprocessable_entity
37
37
  end
@@ -39,7 +39,15 @@ module JSAdmin
39
39
 
40
40
  def destroy
41
41
  @record.destroy
42
- redirect_to resources_path(@resource.model_id), notice: "#{@resource.singular_label} was deleted."
42
+ message = t("js_admin.resources.notices.deleted", resource: @resource.singular_label)
43
+
44
+ respond_to do |format|
45
+ format.turbo_stream do
46
+ flash.now[:notice] = message
47
+ load_records
48
+ end
49
+ format.html { redirect_to resources_path(@resource.model_id), notice: message }
50
+ end
43
51
  end
44
52
 
45
53
  private
@@ -52,6 +60,13 @@ module JSAdmin
52
60
  @record = @resource.find(params[:id])
53
61
  end
54
62
 
63
+ def load_records
64
+ @total_count = @resource.count
65
+ @total_pages = @resource.total_pages(total_count: @total_count)
66
+ @page = [ [ params.fetch(:page, 1).to_i, 1 ].max, @total_pages ].min
67
+ @records = @resource.records(page: @page)
68
+ end
69
+
55
70
  def resource_params
56
71
  params
57
72
  .fetch(@resource.param_key, ActionController::Parameters.new)