js_admin 0.0.1 → 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +179 -19
- data/app/assets/javascripts/js_admin/application.js +13 -0
- data/app/assets/javascripts/js_admin/controllers/flash_controller.js +23 -0
- data/app/assets/javascripts/js_admin/controllers/theme_controller.js +62 -0
- data/app/assets/tailwind/js_admin/engine.css +4 -0
- data/app/controllers/js_admin/application_controller.rb +28 -0
- data/app/controllers/js_admin/home_controller.rb +6 -0
- data/app/controllers/js_admin/resources_controller.rb +17 -4
- data/app/helpers/js_admin/application_helper.rb +286 -19
- data/app/views/js_admin/home/index.html.erb +5 -0
- data/app/views/js_admin/models/_index_content.html.erb +20 -0
- data/app/views/js_admin/models/index.html.erb +3 -20
- data/app/views/js_admin/resources/_edit_content.html.erb +11 -0
- data/app/views/js_admin/resources/_form.html.erb +20 -13
- data/app/views/js_admin/resources/_index_content.html.erb +74 -0
- data/app/views/js_admin/resources/_new_content.html.erb +10 -0
- data/app/views/js_admin/resources/_show_content.html.erb +26 -0
- data/app/views/js_admin/resources/destroy.turbo_stream.erb +7 -0
- data/app/views/js_admin/resources/edit.html.erb +3 -11
- data/app/views/js_admin/resources/index.html.erb +3 -52
- data/app/views/js_admin/resources/new.html.erb +3 -10
- data/app/views/js_admin/resources/show.html.erb +3 -26
- data/app/views/layouts/js_admin/_content_frame.html.erb +7 -0
- data/app/views/layouts/js_admin/_flash.html.erb +3 -0
- data/app/views/layouts/js_admin/_flash_messages.html.erb +9 -0
- data/app/views/layouts/js_admin/_sidebar.html.erb +36 -0
- data/app/views/layouts/js_admin/application.html.erb +19 -17
- data/config/routes.rb +2 -1
- data/lib/generators/js_admin/install/install_generator.rb +63 -5
- data/lib/js_admin/configuration.rb +16 -3
- data/lib/js_admin/field.rb +4 -4
- data/lib/js_admin/resource.rb +12 -4
- data/lib/js_admin/version.rb +1 -1
- data/lib/js_admin.rb +6 -0
- metadata +37 -2
|
@@ -1,53 +1,245 @@
|
|
|
1
1
|
module JSAdmin
|
|
2
2
|
module ApplicationHelper
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
CONTENT_FRAME_ID = "js_admin_content"
|
|
4
|
+
CONTENT_BODY_ID = "js_admin_content_body"
|
|
5
|
+
FLASH_ID = "js_admin_flash"
|
|
6
|
+
THEME_COOKIE_NAME = "js_admin_theme"
|
|
7
|
+
|
|
8
|
+
TEXT_INPUT_CLASSES = [
|
|
9
|
+
"w-full rounded-md border border-slate-300 bg-white px-3.5 py-2.5 text-sm text-slate-950 shadow-sm",
|
|
10
|
+
"placeholder:text-slate-400 focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200",
|
|
11
|
+
"dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:placeholder:text-slate-500",
|
|
12
|
+
"dark:focus:border-sky-400 dark:focus:ring-sky-900"
|
|
13
|
+
].join(" ")
|
|
14
|
+
|
|
15
|
+
SELECT_INPUT_CLASSES = [
|
|
16
|
+
"w-full rounded-md border border-slate-300 bg-white px-3.5 py-2.5 text-sm text-slate-950 shadow-sm",
|
|
17
|
+
"focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200",
|
|
18
|
+
"dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100",
|
|
19
|
+
"dark:focus:border-sky-400 dark:focus:ring-sky-900"
|
|
20
|
+
].join(" ")
|
|
21
|
+
|
|
22
|
+
CHECKBOX_INPUT_CLASSES = [
|
|
23
|
+
"h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500",
|
|
24
|
+
"dark:border-slate-700 dark:bg-slate-900 dark:focus:ring-sky-700"
|
|
25
|
+
].join(" ")
|
|
26
|
+
|
|
27
|
+
TEXT_INPUT_ERROR_CLASSES = [
|
|
28
|
+
"w-full rounded-md border border-rose-400 bg-white px-3.5 py-2.5 text-sm text-slate-950 shadow-sm",
|
|
29
|
+
"placeholder:text-slate-400 focus:border-rose-500 focus:outline-none focus:ring-2 focus:ring-rose-200",
|
|
30
|
+
"dark:border-rose-500 dark:bg-slate-900 dark:text-slate-100 dark:placeholder:text-slate-500",
|
|
31
|
+
"dark:focus:border-rose-400 dark:focus:ring-rose-950"
|
|
32
|
+
].join(" ")
|
|
33
|
+
|
|
34
|
+
SELECT_INPUT_ERROR_CLASSES = [
|
|
35
|
+
"w-full rounded-md border border-rose-400 bg-white px-3.5 py-2.5 text-sm text-slate-950 shadow-sm",
|
|
36
|
+
"focus:border-rose-500 focus:outline-none focus:ring-2 focus:ring-rose-200",
|
|
37
|
+
"dark:border-rose-500 dark:bg-slate-900 dark:text-slate-100",
|
|
38
|
+
"dark:focus:border-rose-400 dark:focus:ring-rose-950"
|
|
39
|
+
].join(" ")
|
|
40
|
+
|
|
41
|
+
CHECKBOX_INPUT_ERROR_CLASSES = [
|
|
42
|
+
"h-4 w-4 rounded border-rose-400 text-rose-600 focus:ring-rose-500",
|
|
43
|
+
"dark:border-rose-500 dark:bg-slate-900 dark:focus:ring-rose-900"
|
|
44
|
+
].join(" ")
|
|
45
|
+
|
|
46
|
+
FIELD_LABEL_CLASSES = "text-sm font-medium text-slate-700 dark:text-slate-300 sm:col-span-4"
|
|
47
|
+
FIELD_LABEL_ERROR_CLASSES = "text-sm font-medium text-rose-700 dark:text-rose-300 sm:col-span-4"
|
|
48
|
+
FIELD_ERROR_TEXT_CLASSES = "mt-2 text-sm text-rose-600 dark:text-rose-300"
|
|
49
|
+
FORM_ERROR_CLASSES = "rounded-md border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200"
|
|
50
|
+
|
|
51
|
+
SIDEBAR_LINK_BASE_CLASSES = [
|
|
52
|
+
"block rounded-md px-3 py-2 text-sm font-medium",
|
|
53
|
+
"hover:bg-slate-100 hover:text-slate-950",
|
|
54
|
+
"dark:hover:bg-slate-800 dark:hover:text-slate-100"
|
|
55
|
+
].join(" ")
|
|
56
|
+
|
|
57
|
+
SIDEBAR_LINK_ACTIVE_CLASSES = "bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-200"
|
|
58
|
+
SIDEBAR_LINK_INACTIVE_CLASSES = "text-slate-700 dark:text-slate-300"
|
|
59
|
+
|
|
60
|
+
FLASH_BASE_CLASSES = [
|
|
61
|
+
"transform rounded-md border px-4 py-3 text-sm shadow-lg transition duration-300 ease-out"
|
|
62
|
+
].join(" ")
|
|
63
|
+
|
|
64
|
+
FLASH_CLASSES = {
|
|
65
|
+
notice: "border-emerald-200 bg-emerald-50 text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950 dark:text-emerald-200",
|
|
66
|
+
alert: "border-rose-200 bg-rose-50 text-rose-800 dark:border-rose-900 dark:bg-rose-950 dark:text-rose-200"
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
PAGINATION_LINK_CLASSES = [
|
|
70
|
+
"inline-flex h-9 min-w-9 items-center justify-center rounded-md border border-slate-300 bg-white px-3 text-sm font-medium",
|
|
71
|
+
"text-slate-700 shadow-sm hover:bg-slate-50",
|
|
72
|
+
"dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800"
|
|
73
|
+
].join(" ")
|
|
74
|
+
|
|
75
|
+
PAGINATION_DISABLED_CLASSES = [
|
|
76
|
+
"inline-flex h-9 min-w-9 items-center justify-center rounded-md border border-slate-200 bg-slate-100 px-3 text-sm font-medium",
|
|
77
|
+
"text-slate-400 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-600"
|
|
78
|
+
].join(" ")
|
|
79
|
+
|
|
80
|
+
PAGINATION_STATUS_CLASSES = [
|
|
81
|
+
"inline-flex h-9 min-w-28 items-center justify-center rounded-md border border-slate-200 bg-slate-50 px-3 text-sm font-medium",
|
|
82
|
+
"text-slate-700 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-200"
|
|
83
|
+
].join(" ")
|
|
6
84
|
|
|
7
85
|
def js_admin_field_input(form, field)
|
|
8
86
|
case field.input_kind
|
|
9
87
|
when :text_area
|
|
10
|
-
|
|
88
|
+
tag.textarea(js_admin_field_value(form, field), **js_admin_input_options(form, field, js_admin_text_input_classes(form, field)), rows: 5)
|
|
11
89
|
when :check_box
|
|
12
90
|
tag.div(class: "flex h-10 items-center") do
|
|
13
|
-
|
|
91
|
+
safe_join([
|
|
92
|
+
tag.input(type: "hidden", name: js_admin_field_name(form, field), value: "0", autocomplete: "off"),
|
|
93
|
+
tag.input(**js_admin_input_options(form, field, js_admin_checkbox_input_classes(form, field)).merge(type: "checkbox", value: "1", checked: js_admin_checkbox_checked?(form, field)))
|
|
94
|
+
])
|
|
14
95
|
end
|
|
15
96
|
when :number_field
|
|
16
|
-
|
|
97
|
+
tag.input(**js_admin_input_options(form, field, js_admin_text_input_classes(form, field)).merge(type: "number", step: field.number_step, value: js_admin_field_value(form, field)))
|
|
17
98
|
when :date_field
|
|
18
|
-
|
|
99
|
+
tag.input(**js_admin_input_options(form, field, js_admin_text_input_classes(form, field)).merge(type: "date", value: js_admin_formatted_field_value(form, field, "%Y-%m-%d")))
|
|
19
100
|
when :datetime_field
|
|
20
|
-
|
|
101
|
+
tag.input(**js_admin_input_options(form, field, js_admin_text_input_classes(form, field)).merge(type: "datetime-local", value: js_admin_formatted_field_value(form, field, "%Y-%m-%dT%H:%M:%S")))
|
|
21
102
|
when :time_field
|
|
22
|
-
|
|
103
|
+
tag.input(**js_admin_input_options(form, field, js_admin_text_input_classes(form, field)).merge(type: "time", value: js_admin_formatted_field_value(form, field, "%H:%M:%S")))
|
|
23
104
|
when :enum_select
|
|
24
|
-
form
|
|
105
|
+
select_tag(js_admin_field_name(form, field), options_for_select(field.enum_values, js_admin_field_value(form, field)), js_admin_select_options(form, field))
|
|
25
106
|
when :association_select
|
|
26
|
-
form
|
|
107
|
+
select_tag(js_admin_field_name(form, field), options_for_select(field.association_options, js_admin_field_value(form, field)), js_admin_select_options(form, field))
|
|
27
108
|
else
|
|
28
|
-
|
|
109
|
+
tag.input(**js_admin_input_options(form, field, js_admin_text_input_classes(form, field)).merge(type: "text", value: js_admin_field_value(form, field)))
|
|
29
110
|
end
|
|
30
111
|
end
|
|
31
112
|
|
|
113
|
+
def js_admin_field_id(form, field)
|
|
114
|
+
"#{form.object_name}_#{field.name}".tr("][", "_").delete_suffix("_")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def js_admin_field_error_id(form, field)
|
|
118
|
+
"#{js_admin_field_id(form, field)}_error"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def js_admin_field_errors(record, field)
|
|
122
|
+
record.errors[field.name].map(&:to_s)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def js_admin_form_errors(record, resource)
|
|
126
|
+
editable_attributes = resource.editable_fields.map { |field| field.name.to_sym }
|
|
127
|
+
|
|
128
|
+
record.errors.filter_map do |error|
|
|
129
|
+
next if editable_attributes.include?(error.attribute.to_sym)
|
|
130
|
+
|
|
131
|
+
error.full_message
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def js_admin_field_label_classes(record, field)
|
|
136
|
+
js_admin_field_errors(record, field).any? ? FIELD_LABEL_ERROR_CLASSES : FIELD_LABEL_CLASSES
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def js_admin_field_error_text_classes
|
|
140
|
+
FIELD_ERROR_TEXT_CLASSES
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def js_admin_form_error_classes
|
|
144
|
+
FORM_ERROR_CLASSES
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def js_admin_content_frame_id
|
|
148
|
+
CONTENT_FRAME_ID
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def js_admin_content_body_id
|
|
152
|
+
CONTENT_BODY_ID
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def js_admin_flash_id
|
|
156
|
+
FLASH_ID
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def js_admin_content_frame_data
|
|
160
|
+
{
|
|
161
|
+
turbo_action: "advance"
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def js_admin_content_link_data
|
|
166
|
+
{
|
|
167
|
+
turbo_frame: js_admin_content_frame_id,
|
|
168
|
+
turbo_action: "advance"
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
|
|
32
172
|
def js_admin_table_value(field, record)
|
|
33
173
|
value = field.formatted_value(record)
|
|
34
|
-
classes = value == "NULL" ? "text-slate-400" : "text-slate-700"
|
|
174
|
+
classes = value == "NULL" ? "text-slate-400 dark:text-slate-500" : "text-slate-700 dark:text-slate-200"
|
|
35
175
|
|
|
36
176
|
tag.span(truncate(value, length: 80), class: classes)
|
|
37
177
|
end
|
|
38
178
|
|
|
179
|
+
def js_admin_sidebar_top_level_resources
|
|
180
|
+
js_admin_sidebar_resources.select { |resource| js_admin_resource_namespace(resource).blank? }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def js_admin_sidebar_namespace_groups
|
|
184
|
+
js_admin_sidebar_resources
|
|
185
|
+
.reject { |resource| js_admin_resource_namespace(resource).blank? }
|
|
186
|
+
.group_by { |resource| js_admin_resource_namespace(resource) }
|
|
187
|
+
.sort_by { |namespace, _resources| namespace }
|
|
188
|
+
.to_h
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def js_admin_sidebar_resource_label(resource)
|
|
192
|
+
resource.model_class.name.demodulize.underscore.humanize
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def js_admin_sidebar_namespace_label(namespace)
|
|
196
|
+
namespace.split("::").map { |name| name.underscore.humanize }.join(" / ")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def js_admin_sidebar_namespace_open?(resources)
|
|
200
|
+
resources.any? { |resource| js_admin_sidebar_resource_active?(resource) }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def js_admin_sidebar_link_classes(resource)
|
|
204
|
+
state_classes = if js_admin_sidebar_resource_active?(resource)
|
|
205
|
+
SIDEBAR_LINK_ACTIVE_CLASSES
|
|
206
|
+
else
|
|
207
|
+
SIDEBAR_LINK_INACTIVE_CLASSES
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
"#{SIDEBAR_LINK_BASE_CLASSES} #{state_classes}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def js_admin_flash_classes(type)
|
|
214
|
+
[ FLASH_BASE_CLASSES, FLASH_CLASSES.fetch(type.to_sym) ].join(" ")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def js_admin_pagination_link_classes
|
|
218
|
+
PAGINATION_LINK_CLASSES
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def js_admin_pagination_disabled_classes
|
|
222
|
+
PAGINATION_DISABLED_CLASSES
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def js_admin_pagination_status_classes
|
|
226
|
+
PAGINATION_STATUS_CLASSES
|
|
227
|
+
end
|
|
228
|
+
|
|
39
229
|
def js_admin_stylesheet_link_tag(logical_name)
|
|
40
230
|
return unless js_admin_asset_exists?("#{logical_name}.css")
|
|
41
231
|
|
|
42
232
|
stylesheet_link_tag logical_name, "data-turbo-track": "reload"
|
|
43
233
|
end
|
|
44
234
|
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
235
|
+
def js_admin_javascript_tags
|
|
236
|
+
return unless respond_to?(:javascript_importmap_tags)
|
|
237
|
+
|
|
238
|
+
javascript_importmap_tags "js_admin"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def js_admin_html_classes
|
|
242
|
+
"dark" if cookies[THEME_COOKIE_NAME] == "dark"
|
|
51
243
|
end
|
|
52
244
|
|
|
53
245
|
def js_admin_asset_exists?(logical_path)
|
|
@@ -63,5 +255,80 @@ module JSAdmin
|
|
|
63
255
|
rescue StandardError
|
|
64
256
|
false
|
|
65
257
|
end
|
|
258
|
+
|
|
259
|
+
private
|
|
260
|
+
|
|
261
|
+
def js_admin_field_name(form, field)
|
|
262
|
+
"#{form.object_name}[#{field.name}]"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def js_admin_input_options(form, field, input_classes)
|
|
266
|
+
js_admin_field_html_options(form, field, input_classes).merge(name: js_admin_field_name(form, field))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def js_admin_select_options(form, field)
|
|
270
|
+
js_admin_field_html_options(form, field, js_admin_select_input_classes(form, field)).merge(include_blank: true)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def js_admin_field_html_options(form, field, input_classes)
|
|
274
|
+
options = {
|
|
275
|
+
id: js_admin_field_id(form, field),
|
|
276
|
+
class: input_classes
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if js_admin_field_errors(form.object, field).any?
|
|
280
|
+
options[:aria] = {
|
|
281
|
+
invalid: "true",
|
|
282
|
+
describedby: js_admin_field_error_id(form, field)
|
|
283
|
+
}
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
options
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def js_admin_text_input_classes(form, field)
|
|
290
|
+
js_admin_field_errors(form.object, field).any? ? TEXT_INPUT_ERROR_CLASSES : TEXT_INPUT_CLASSES
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def js_admin_select_input_classes(form, field)
|
|
294
|
+
js_admin_field_errors(form.object, field).any? ? SELECT_INPUT_ERROR_CLASSES : SELECT_INPUT_CLASSES
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def js_admin_checkbox_input_classes(form, field)
|
|
298
|
+
js_admin_field_errors(form.object, field).any? ? CHECKBOX_INPUT_ERROR_CLASSES : CHECKBOX_INPUT_CLASSES
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def js_admin_field_value(form, field)
|
|
302
|
+
before_type_cast_method = "#{field.name}_before_type_cast"
|
|
303
|
+
|
|
304
|
+
if form.object.respond_to?(before_type_cast_method)
|
|
305
|
+
form.object.public_send(before_type_cast_method)
|
|
306
|
+
else
|
|
307
|
+
form.object.public_send(field.name)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def js_admin_formatted_field_value(form, field, format)
|
|
312
|
+
value = js_admin_field_value(form, field)
|
|
313
|
+
return value if value.blank? || !value.respond_to?(:strftime)
|
|
314
|
+
|
|
315
|
+
value.strftime(format)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def js_admin_checkbox_checked?(form, field)
|
|
319
|
+
ActiveModel::Type::Boolean.new.cast(js_admin_field_value(form, field))
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def js_admin_sidebar_resources
|
|
323
|
+
JSAdmin.resources.sort_by { |resource| resource.model_class.name }
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def js_admin_resource_namespace(resource)
|
|
327
|
+
resource.model_class.name.deconstantize
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def js_admin_sidebar_resource_active?(resource)
|
|
331
|
+
params[:model_id] == resource.model_id
|
|
332
|
+
end
|
|
66
333
|
end
|
|
67
334
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<div class="mb-8">
|
|
2
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100">Models</h1>
|
|
3
|
+
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400"><%= pluralize(@resources.size, "model") %> available</p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
7
|
+
<ul class="divide-y divide-slate-200 dark:divide-slate-800">
|
|
8
|
+
<% @resources.each do |resource| %>
|
|
9
|
+
<li>
|
|
10
|
+
<%= link_to resources_path(resource.model_id), class: "flex items-center justify-between gap-6 px-5 py-5 hover:bg-slate-50 dark:hover:bg-slate-800/70 sm:px-6" do %>
|
|
11
|
+
<span>
|
|
12
|
+
<span class="block text-sm font-medium text-slate-950 dark:text-slate-100"><%= resource.label %></span>
|
|
13
|
+
<span class="mt-1.5 block text-xs text-slate-500 dark:text-slate-400"><%= resource.model_class.name %></span>
|
|
14
|
+
</span>
|
|
15
|
+
<span class="text-sm text-slate-500 dark:text-slate-400"><%= number_with_delimiter(resource.count) %></span>
|
|
16
|
+
<% end %>
|
|
17
|
+
</li>
|
|
18
|
+
<% end %>
|
|
19
|
+
</ul>
|
|
20
|
+
</div>
|
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
<% content_for :title, "JSAdmin Models" %>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
</div>
|
|
7
|
-
|
|
8
|
-
<div class="overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm">
|
|
9
|
-
<ul class="divide-y divide-slate-200">
|
|
10
|
-
<% @resources.each do |resource| %>
|
|
11
|
-
<li>
|
|
12
|
-
<%= link_to resources_path(resource.model_id), class: "flex items-center justify-between px-4 py-4 hover:bg-slate-50" do %>
|
|
13
|
-
<span>
|
|
14
|
-
<span class="block text-sm font-medium text-slate-950"><%= resource.label %></span>
|
|
15
|
-
<span class="mt-1 block text-xs text-slate-500"><%= resource.model_class.name %></span>
|
|
16
|
-
</span>
|
|
17
|
-
<span class="text-sm text-slate-500"><%= number_with_delimiter(resource.count) %></span>
|
|
18
|
-
<% end %>
|
|
19
|
-
</li>
|
|
20
|
-
<% end %>
|
|
21
|
-
</ul>
|
|
22
|
-
</div>
|
|
3
|
+
<%= render layout: "layouts/js_admin/content_frame" do %>
|
|
4
|
+
<%= render "index_content" %>
|
|
5
|
+
<% end %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="mb-8">
|
|
2
|
+
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400">
|
|
3
|
+
<%= link_to "Home", root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
4
|
+
<%= link_to @resource.label, resources_path(@resource.model_id), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
5
|
+
<%= link_to @resource.display_name(@record), resource_path(@resource.model_id, @record), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
6
|
+
Edit
|
|
7
|
+
</div>
|
|
8
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100">Edit <%= @resource.display_name(@record) %></h1>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<%= render "form", record: @record, resource: @resource, url: resource_path(@resource.model_id, @record) %>
|
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
<%= form_with model: record, url: url, class: "space-y-
|
|
2
|
-
<%
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<%= form_with model: record, url: url, class: "space-y-7" do |form| %>
|
|
2
|
+
<% form_errors = js_admin_form_errors(record, resource) %>
|
|
3
|
+
|
|
4
|
+
<% if form_errors.any? %>
|
|
5
|
+
<div class="<%= js_admin_form_error_classes %>" role="alert">
|
|
6
|
+
<ul class="list-disc space-y-1 pl-5">
|
|
7
|
+
<% form_errors.each do |message| %>
|
|
7
8
|
<li><%= message %></li>
|
|
8
9
|
<% end %>
|
|
9
10
|
</ul>
|
|
10
11
|
</div>
|
|
11
12
|
<% end %>
|
|
12
13
|
|
|
13
|
-
<div class="rounded-lg border border-slate-200 bg-white shadow-sm">
|
|
14
|
-
<div class="divide-y divide-slate-200">
|
|
14
|
+
<div class="rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
15
|
+
<div class="divide-y divide-slate-200 dark:divide-slate-800">
|
|
15
16
|
<% resource.editable_fields.each do |field| %>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
<% field_errors = js_admin_field_errors(record, field) %>
|
|
18
|
+
|
|
19
|
+
<div class="grid gap-3 px-5 py-5 sm:grid-cols-12 sm:gap-6 sm:px-6">
|
|
20
|
+
<%= tag.label field.human_name, for: js_admin_field_id(form, field), class: js_admin_field_label_classes(record, field) %>
|
|
21
|
+
<div class="sm:col-span-8">
|
|
19
22
|
<%= js_admin_field_input(form, field) %>
|
|
23
|
+
|
|
24
|
+
<% if field_errors.any? %>
|
|
25
|
+
<p id="<%= js_admin_field_error_id(form, field) %>" class="<%= js_admin_field_error_text_classes %>"><%= field_errors.to_sentence %></p>
|
|
26
|
+
<% end %>
|
|
20
27
|
</div>
|
|
21
28
|
</div>
|
|
22
29
|
<% end %>
|
|
@@ -24,7 +31,7 @@
|
|
|
24
31
|
</div>
|
|
25
32
|
|
|
26
33
|
<div class="flex items-center gap-2">
|
|
27
|
-
<%= form.submit class: "inline-flex items-center justify-center rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-sky-700" %>
|
|
28
|
-
<%= link_to "Cancel", record.persisted? ? resource_path(resource.model_id, record) : resources_path(resource.model_id), class: "inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50" %>
|
|
34
|
+
<%= form.submit class: "inline-flex items-center justify-center rounded-md bg-sky-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-sky-700 dark:bg-sky-500 dark:hover:bg-sky-400" %>
|
|
35
|
+
<%= link_to "Cancel", record.persisted? ? resource_path(resource.model_id, record) : resources_path(resource.model_id), class: "inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800" %>
|
|
29
36
|
</div>
|
|
30
37
|
<% end %>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<div class="mb-8 flex flex-col gap-5 sm:flex-row sm:items-center sm:justify-between">
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400"><%= link_to "Home", root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> / <%= @resource.label %></div>
|
|
4
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"><%= @resource.label %></h1>
|
|
5
|
+
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400"><%= number_with_delimiter(@total_count) %> records</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<%= link_to "New #{@resource.singular_label}", new_resource_path(@resource.model_id), class: "inline-flex items-center justify-center rounded-md bg-sky-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-sky-700 dark:bg-sky-500 dark:hover:bg-sky-400" %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="overflow-x-auto rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
12
|
+
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-800">
|
|
13
|
+
<thead class="bg-slate-50 dark:bg-slate-900">
|
|
14
|
+
<tr>
|
|
15
|
+
<% @resource.list_fields.each do |field| %>
|
|
16
|
+
<th scope="col" class="whitespace-nowrap px-5 py-4 text-left text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400"><%= field.human_name %></th>
|
|
17
|
+
<% end %>
|
|
18
|
+
<th scope="col" class="w-44 px-5 py-4 text-right text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Actions</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody class="divide-y divide-slate-200 bg-white dark:divide-slate-800 dark:bg-slate-900">
|
|
22
|
+
<% @records.each do |record| %>
|
|
23
|
+
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/70">
|
|
24
|
+
<% @resource.list_fields.each do |field| %>
|
|
25
|
+
<td class="whitespace-nowrap px-5 py-4 text-sm"><%= js_admin_table_value(field, record) %></td>
|
|
26
|
+
<% end %>
|
|
27
|
+
<td class="whitespace-nowrap px-5 py-4 text-right text-sm">
|
|
28
|
+
<div class="flex items-center justify-end gap-3">
|
|
29
|
+
<%= link_to "View", resource_path(@resource.model_id, record), class: "font-medium text-sky-700 hover:text-sky-900 dark:text-sky-300 dark:hover:text-sky-200" %>
|
|
30
|
+
<%= link_to "Edit", edit_resource_path(@resource.model_id, record), class: "font-medium text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-slate-100" %>
|
|
31
|
+
<%= button_to "Delete", resource_path(@resource.model_id, record), method: :delete, form: { class: "inline", data: { turbo_confirm: "Delete this record?" } }, class: "font-medium text-rose-700 hover:text-rose-900 dark:text-rose-300 dark:hover:text-rose-200" %>
|
|
32
|
+
</div>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<% end %>
|
|
36
|
+
|
|
37
|
+
<% if @records.empty? %>
|
|
38
|
+
<tr>
|
|
39
|
+
<td colspan="<%= @resource.list_fields.size + 1 %>" class="px-5 py-12 text-center text-sm text-slate-500 dark:text-slate-400">No records</td>
|
|
40
|
+
</tr>
|
|
41
|
+
<% end %>
|
|
42
|
+
</tbody>
|
|
43
|
+
</table>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="mt-6 flex justify-end">
|
|
47
|
+
<nav class="flex flex-wrap items-center justify-end gap-2" aria-label="Pagination">
|
|
48
|
+
<% if @page > 1 %>
|
|
49
|
+
<%= link_to "First", resources_path(@resource.model_id, page: 1), class: js_admin_pagination_link_classes %>
|
|
50
|
+
<% else %>
|
|
51
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true">First</span>
|
|
52
|
+
<% end %>
|
|
53
|
+
|
|
54
|
+
<% if @resource.previous_page?(@page) %>
|
|
55
|
+
<%= link_to "Previous", resources_path(@resource.model_id, page: @page - 1), class: js_admin_pagination_link_classes %>
|
|
56
|
+
<% else %>
|
|
57
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true">Previous</span>
|
|
58
|
+
<% end %>
|
|
59
|
+
|
|
60
|
+
<span class="<%= js_admin_pagination_status_classes %>" aria-current="page" aria-label="Page <%= @page %> of <%= @total_pages %>"><%= @page %> / <%= @total_pages %></span>
|
|
61
|
+
|
|
62
|
+
<% if @resource.next_page?(@page) %>
|
|
63
|
+
<%= link_to "Next", resources_path(@resource.model_id, page: @page + 1), class: js_admin_pagination_link_classes %>
|
|
64
|
+
<% else %>
|
|
65
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true">Next</span>
|
|
66
|
+
<% end %>
|
|
67
|
+
|
|
68
|
+
<% if @page < @total_pages %>
|
|
69
|
+
<%= link_to "Last", resources_path(@resource.model_id, page: @total_pages), class: js_admin_pagination_link_classes %>
|
|
70
|
+
<% else %>
|
|
71
|
+
<span class="<%= js_admin_pagination_disabled_classes %>" aria-disabled="true">Last</span>
|
|
72
|
+
<% end %>
|
|
73
|
+
</nav>
|
|
74
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<div class="mb-8">
|
|
2
|
+
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400">
|
|
3
|
+
<%= link_to "Home", root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
4
|
+
<%= link_to @resource.label, resources_path(@resource.model_id), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
5
|
+
New
|
|
6
|
+
</div>
|
|
7
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100">New <%= @resource.singular_label %></h1>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<%= render "form", record: @record, resource: @resource, url: resources_path(@resource.model_id) %>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div class="mb-8 flex flex-col gap-5 sm:flex-row sm:items-center sm:justify-between">
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-2 text-sm text-slate-500 dark:text-slate-400">
|
|
4
|
+
<%= link_to "Home", root_path, class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
5
|
+
<%= link_to @resource.label, resources_path(@resource.model_id), class: "hover:text-slate-950 dark:hover:text-slate-100" %> /
|
|
6
|
+
<%= @resource.display_name(@record) %>
|
|
7
|
+
</div>
|
|
8
|
+
<h1 class="text-2xl font-semibold text-slate-950 dark:text-slate-100"><%= @resource.display_name(@record) %></h1>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="flex gap-2">
|
|
12
|
+
<%= link_to "Edit", edit_resource_path(@resource.model_id, @record), class: "inline-flex items-center justify-center rounded-md border border-slate-300 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800" %>
|
|
13
|
+
<%= button_to "Delete", resource_path(@resource.model_id, @record), method: :delete, form: { data: { turbo_confirm: "Delete this record?" } }, class: "inline-flex items-center justify-center rounded-md bg-rose-600 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-rose-700 dark:bg-rose-500 dark:hover:bg-rose-400" %>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
|
|
18
|
+
<dl class="divide-y divide-slate-200 dark:divide-slate-800">
|
|
19
|
+
<% @resource.fields.each do |field| %>
|
|
20
|
+
<div class="grid gap-3 px-5 py-5 sm:grid-cols-12 sm:gap-6 sm:px-6">
|
|
21
|
+
<dt class="text-sm font-medium text-slate-600 dark:text-slate-300 sm:col-span-4"><%= field.human_name %></dt>
|
|
22
|
+
<dd class="text-sm text-slate-950 dark:text-slate-100 sm:col-span-8"><%= js_admin_table_value(field, @record) %></dd>
|
|
23
|
+
</div>
|
|
24
|
+
<% end %>
|
|
25
|
+
</dl>
|
|
26
|
+
</div>
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
<% content_for :title, "Edit #{@resource.display_name(@record)}" %>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<%= link_to @resource.label, resources_path(@resource.model_id), class: "hover:text-slate-950" %> /
|
|
7
|
-
<%= link_to @resource.display_name(@record), resource_path(@resource.model_id, @record), class: "hover:text-slate-950" %> /
|
|
8
|
-
Edit
|
|
9
|
-
</div>
|
|
10
|
-
<h1 class="text-2xl font-semibold text-slate-950">Edit <%= @resource.display_name(@record) %></h1>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
<%= render "form", record: @record, resource: @resource, url: resource_path(@resource.model_id, @record) %>
|
|
3
|
+
<%= render layout: "layouts/js_admin/content_frame" do %>
|
|
4
|
+
<%= render "edit_content" %>
|
|
5
|
+
<% end %>
|