quick_admin 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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +46 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +270 -0
  5. data/Rakefile +6 -0
  6. data/app/assets/javascripts/quick_admin/application.js +245 -0
  7. data/app/assets/javascripts/quick_admin/controllers/alert_controller.js +28 -0
  8. data/app/assets/javascripts/quick_admin/controllers/bulk_actions_controller.js +49 -0
  9. data/app/assets/javascripts/quick_admin/controllers/modal_controller.js +35 -0
  10. data/app/assets/javascripts/quick_admin/controllers/search_controller.js +26 -0
  11. data/app/assets/javascripts/quick_admin/controllers/text_expander_controller.js +22 -0
  12. data/app/assets/stylesheets/quick_admin/application.css +617 -0
  13. data/app/controllers/quick_admin/application_controller.rb +34 -0
  14. data/app/controllers/quick_admin/dashboard_controller.rb +20 -0
  15. data/app/controllers/quick_admin/resources_controller.rb +229 -0
  16. data/app/helpers/quick_admin/application_helper.rb +141 -0
  17. data/app/views/layouts/quick_admin/application.html.erb +41 -0
  18. data/app/views/quick_admin/dashboard/index.html.erb +36 -0
  19. data/app/views/quick_admin/resources/_form.html.erb +39 -0
  20. data/app/views/quick_admin/resources/_pagination.html.erb +29 -0
  21. data/app/views/quick_admin/resources/_resource_row.html.erb +32 -0
  22. data/app/views/quick_admin/resources/_resources_list.html.erb +58 -0
  23. data/app/views/quick_admin/resources/attachment.html.erb +21 -0
  24. data/app/views/quick_admin/resources/bulk_destroy.turbo_stream.erb +4 -0
  25. data/app/views/quick_admin/resources/create.turbo_stream.erb +9 -0
  26. data/app/views/quick_admin/resources/destroy.turbo_stream.erb +4 -0
  27. data/app/views/quick_admin/resources/edit.html.erb +14 -0
  28. data/app/views/quick_admin/resources/index.html.erb +38 -0
  29. data/app/views/quick_admin/resources/index.turbo_stream.erb +3 -0
  30. data/app/views/quick_admin/resources/new.html.erb +14 -0
  31. data/app/views/quick_admin/resources/show.html.erb +38 -0
  32. data/app/views/quick_admin/resources/update.turbo_stream.erb +9 -0
  33. data/config/routes.rb +14 -0
  34. data/lib/generators/quick_admin/install_generator.rb +37 -0
  35. data/lib/generators/quick_admin/templates/quick_admin.rb +43 -0
  36. data/lib/quick_admin/configuration.rb +59 -0
  37. data/lib/quick_admin/engine.rb +20 -0
  38. data/lib/quick_admin/resource.rb +102 -0
  39. data/lib/quick_admin/version.rb +3 -0
  40. data/lib/quick_admin.rb +70 -0
  41. metadata +146 -0
@@ -0,0 +1,35 @@
1
+ import { Controller } from "@hotwire/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.element.addEventListener("turbo:submit-end", this.handleSubmitEnd.bind(this))
6
+ document.body.style.overflow = "hidden"
7
+ }
8
+
9
+ disconnect() {
10
+ document.body.style.overflow = ""
11
+ }
12
+
13
+ close(event) {
14
+ if (event) {
15
+ event.preventDefault()
16
+ }
17
+ this.element.remove()
18
+ }
19
+
20
+ stopPropagation(event) {
21
+ event.stopPropagation()
22
+ }
23
+
24
+ handleSubmitEnd(event) {
25
+ if (event.detail.success) {
26
+ this.close()
27
+ }
28
+ }
29
+
30
+ handleKeydown(event) {
31
+ if (event.key === "Escape") {
32
+ this.close()
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,26 @@
1
+ import { Controller } from "@hotwire/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["input"]
5
+
6
+ connect() {
7
+ this.timeout = null
8
+ }
9
+
10
+ search() {
11
+ clearTimeout(this.timeout)
12
+ this.timeout = setTimeout(() => {
13
+ this.submitForm()
14
+ }, 300)
15
+ }
16
+
17
+ submitForm() {
18
+ this.element.requestSubmit()
19
+ }
20
+
21
+ disconnect() {
22
+ if (this.timeout) {
23
+ clearTimeout(this.timeout)
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,22 @@
1
+ import { Controller } from "@hotwire/stimulus"
2
+
3
+ export default class extends Controller {
4
+ toggle(event) {
5
+ event.preventDefault()
6
+
7
+ const button = event.target
8
+ const textSpan = button.previousElementSibling
9
+ const isExpanded = button.textContent === "Show less"
10
+
11
+ if (isExpanded) {
12
+ textSpan.textContent = textSpan.dataset.truncated
13
+ button.textContent = "Show more"
14
+ } else {
15
+ if (!textSpan.dataset.truncated) {
16
+ textSpan.dataset.truncated = textSpan.textContent
17
+ }
18
+ textSpan.textContent = textSpan.dataset.full || textSpan.textContent
19
+ button.textContent = "Show less"
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,617 @@
1
+ /* QuickAdmin Base Styles */
2
+
3
+ :root {
4
+ --primary-color: #007bff;
5
+ --secondary-color: #6c757d;
6
+ --success-color: #28a745;
7
+ --danger-color: #dc3545;
8
+ --warning-color: #ffc107;
9
+ --info-color: #17a2b8;
10
+ --light-color: #f8f9fa;
11
+ --dark-color: #343a40;
12
+ --border-color: #dee2e6;
13
+ --text-color: #212529;
14
+ --muted-color: #6c757d;
15
+ }
16
+
17
+ * {
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ body {
22
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
23
+ line-height: 1.5;
24
+ color: var(--text-color);
25
+ background-color: #fff;
26
+ margin: 0;
27
+ padding: 0;
28
+ }
29
+
30
+ /* Layout */
31
+ .quick-admin-container {
32
+ display: flex;
33
+ min-height: 100vh;
34
+ }
35
+
36
+ .quick-admin-nav {
37
+ width: 250px;
38
+ background-color: var(--dark-color);
39
+ color: white;
40
+ padding: 1rem;
41
+ position: fixed;
42
+ height: 100vh;
43
+ overflow-y: auto;
44
+ }
45
+
46
+ .quick-admin-main {
47
+ flex: 1;
48
+ margin-left: 250px;
49
+ padding: 2rem;
50
+ }
51
+
52
+ /* Navigation */
53
+ .nav-brand {
54
+ font-size: 1.5rem;
55
+ font-weight: bold;
56
+ color: white;
57
+ text-decoration: none;
58
+ display: block;
59
+ margin-bottom: 2rem;
60
+ padding-bottom: 1rem;
61
+ border-bottom: 1px solid rgba(255,255,255,0.1);
62
+ }
63
+
64
+ .nav-menu {
65
+ list-style: none;
66
+ padding: 0;
67
+ margin: 0;
68
+ }
69
+
70
+ .nav-menu li {
71
+ margin-bottom: 0.5rem;
72
+ }
73
+
74
+ .nav-link {
75
+ color: rgba(255,255,255,0.8);
76
+ text-decoration: none;
77
+ padding: 0.5rem 0;
78
+ display: block;
79
+ border-radius: 4px;
80
+ transition: all 0.2s;
81
+ }
82
+
83
+ .nav-link:hover {
84
+ color: white;
85
+ background-color: rgba(255,255,255,0.1);
86
+ padding-left: 0.5rem;
87
+ }
88
+
89
+ /* Buttons */
90
+ .btn {
91
+ display: inline-block;
92
+ padding: 0.375rem 0.75rem;
93
+ margin-bottom: 0;
94
+ font-size: 0.875rem;
95
+ font-weight: 400;
96
+ line-height: 1.5;
97
+ text-align: center;
98
+ text-decoration: none;
99
+ vertical-align: middle;
100
+ cursor: pointer;
101
+ border: 1px solid transparent;
102
+ border-radius: 0.375rem;
103
+ transition: all 0.15s ease-in-out;
104
+ }
105
+
106
+ .btn-primary {
107
+ color: #fff;
108
+ background-color: var(--primary-color);
109
+ border-color: var(--primary-color);
110
+ }
111
+
112
+ .btn-secondary {
113
+ color: #fff;
114
+ background-color: var(--secondary-color);
115
+ border-color: var(--secondary-color);
116
+ }
117
+
118
+ .btn-success {
119
+ color: #fff;
120
+ background-color: var(--success-color);
121
+ border-color: var(--success-color);
122
+ }
123
+
124
+ .btn-danger {
125
+ color: #fff;
126
+ background-color: var(--danger-color);
127
+ border-color: var(--danger-color);
128
+ }
129
+
130
+ .btn-warning {
131
+ color: var(--dark-color);
132
+ background-color: var(--warning-color);
133
+ border-color: var(--warning-color);
134
+ }
135
+
136
+ .btn-info {
137
+ color: #fff;
138
+ background-color: var(--info-color);
139
+ border-color: var(--info-color);
140
+ }
141
+
142
+ .btn-sm {
143
+ padding: 0.25rem 0.5rem;
144
+ font-size: 0.75rem;
145
+ }
146
+
147
+ .btn:hover {
148
+ opacity: 0.9;
149
+ transform: translateY(-1px);
150
+ }
151
+
152
+ /* Forms */
153
+ .form-control {
154
+ display: block;
155
+ width: 100%;
156
+ padding: 0.375rem 0.75rem;
157
+ font-size: 0.875rem;
158
+ line-height: 1.5;
159
+ color: var(--text-color);
160
+ background-color: #fff;
161
+ border: 1px solid var(--border-color);
162
+ border-radius: 0.375rem;
163
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
164
+ }
165
+
166
+ .form-control:focus {
167
+ border-color: var(--primary-color);
168
+ outline: 0;
169
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
170
+ }
171
+
172
+ .form-group {
173
+ margin-bottom: 1rem;
174
+ }
175
+
176
+ .form-label {
177
+ margin-bottom: 0.5rem;
178
+ font-weight: 500;
179
+ display: block;
180
+ }
181
+
182
+ .form-check {
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 0.5rem;
186
+ }
187
+
188
+ .form-check-input {
189
+ width: auto;
190
+ margin: 0;
191
+ }
192
+
193
+ .form-check-label {
194
+ margin: 0;
195
+ font-weight: normal;
196
+ cursor: pointer;
197
+ }
198
+
199
+ /* Dashboard */
200
+ .dashboard h1 {
201
+ margin-bottom: 2rem;
202
+ }
203
+
204
+ .dashboard-stats {
205
+ display: grid;
206
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
207
+ gap: 1.5rem;
208
+ margin-bottom: 2rem;
209
+ }
210
+
211
+ .stat-card {
212
+ background: white;
213
+ border: 1px solid var(--border-color);
214
+ border-radius: 0.5rem;
215
+ padding: 1.5rem;
216
+ text-align: center;
217
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
218
+ }
219
+
220
+ .stat-card h3 {
221
+ margin: 0 0 1rem 0;
222
+ color: var(--muted-color);
223
+ font-size: 0.875rem;
224
+ text-transform: uppercase;
225
+ letter-spacing: 0.5px;
226
+ }
227
+
228
+ .stat-number {
229
+ font-size: 2rem;
230
+ font-weight: bold;
231
+ color: var(--primary-color);
232
+ margin-bottom: 1rem;
233
+ }
234
+
235
+ .stat-actions {
236
+ display: flex;
237
+ gap: 0.5rem;
238
+ justify-content: center;
239
+ }
240
+
241
+ /* Resources */
242
+ .page-header {
243
+ display: flex;
244
+ justify-content: space-between;
245
+ align-items: center;
246
+ margin-bottom: 2rem;
247
+ padding-bottom: 1rem;
248
+ border-bottom: 1px solid var(--border-color);
249
+ }
250
+
251
+ .page-header h1 {
252
+ margin: 0;
253
+ }
254
+
255
+ .filters-section {
256
+ background: var(--light-color);
257
+ padding: 1rem;
258
+ border-radius: 0.5rem;
259
+ margin-bottom: 1.5rem;
260
+ }
261
+
262
+ .search-form {
263
+ display: flex;
264
+ gap: 1rem;
265
+ align-items: end;
266
+ }
267
+
268
+ .search-input, .filter-select {
269
+ min-width: 200px;
270
+ }
271
+
272
+ /* Table */
273
+ .table-container {
274
+ background: white;
275
+ border: 1px solid var(--border-color);
276
+ border-radius: 0.5rem;
277
+ overflow: hidden;
278
+ }
279
+
280
+ .resources-table {
281
+ width: 100%;
282
+ border-collapse: collapse;
283
+ }
284
+
285
+ .resources-table th,
286
+ .resources-table td {
287
+ padding: 0.75rem;
288
+ text-align: left;
289
+ border-bottom: 1px solid var(--border-color);
290
+ }
291
+
292
+ .resources-table th {
293
+ background-color: var(--light-color);
294
+ font-weight: 600;
295
+ position: sticky;
296
+ top: 0;
297
+ }
298
+
299
+ .resources-table tbody tr:hover {
300
+ background-color: rgba(0, 123, 255, 0.05);
301
+ }
302
+
303
+ .sortable {
304
+ color: var(--text-color);
305
+ text-decoration: none;
306
+ }
307
+
308
+ .sortable:hover {
309
+ color: var(--primary-color);
310
+ }
311
+
312
+ .sortable.sorted {
313
+ color: var(--primary-color);
314
+ font-weight: 600;
315
+ }
316
+
317
+ .actions {
318
+ white-space: nowrap;
319
+ }
320
+
321
+ .actions .btn {
322
+ margin-right: 0.25rem;
323
+ }
324
+
325
+ .inline-form {
326
+ display: inline-block;
327
+ margin: 0;
328
+ }
329
+
330
+ .actions {
331
+ display: flex;
332
+ align-items: center;
333
+ gap: 0.25rem;
334
+ }
335
+
336
+ /* Bulk actions */
337
+ .bulk-actions {
338
+ padding: 1rem;
339
+ background: var(--light-color);
340
+ border-bottom: 1px solid var(--border-color);
341
+ display: flex;
342
+ align-items: center;
343
+ gap: 1rem;
344
+ }
345
+
346
+ .bulk-select {
347
+ display: flex;
348
+ align-items: center;
349
+ gap: 0.5rem;
350
+ margin: 0;
351
+ }
352
+
353
+ /* Pagination */
354
+ .pagination-wrapper {
355
+ padding: 1rem;
356
+ display: flex;
357
+ justify-content: space-between;
358
+ align-items: center;
359
+ background: var(--light-color);
360
+ border-top: 1px solid var(--border-color);
361
+ }
362
+
363
+ .pagination-nav {
364
+ display: flex;
365
+ align-items: center;
366
+ gap: 1rem;
367
+ }
368
+
369
+ .pagination-link {
370
+ color: var(--primary-color);
371
+ text-decoration: none;
372
+ padding: 0.5rem 1rem;
373
+ border: 1px solid var(--border-color);
374
+ border-radius: 0.375rem;
375
+ background: white;
376
+ }
377
+
378
+ .pagination-link:hover {
379
+ background: var(--light-color);
380
+ }
381
+
382
+ .pagination-current {
383
+ color: var(--muted-color);
384
+ }
385
+
386
+ /* Modal */
387
+ .modal-overlay {
388
+ position: fixed;
389
+ top: 0;
390
+ left: 0;
391
+ width: 100%;
392
+ height: 100%;
393
+ background: rgba(0, 0, 0, 0.5);
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: center;
397
+ z-index: 1000;
398
+ }
399
+
400
+ .modal-content {
401
+ background: white;
402
+ border-radius: 0.5rem;
403
+ max-width: 600px;
404
+ width: 90%;
405
+ max-height: 90vh;
406
+ overflow-y: auto;
407
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
408
+ }
409
+
410
+ .modal-header {
411
+ padding: 1.5rem;
412
+ border-bottom: 1px solid var(--border-color);
413
+ display: flex;
414
+ justify-content: space-between;
415
+ align-items: center;
416
+ }
417
+
418
+ .modal-header h2 {
419
+ margin: 0;
420
+ }
421
+
422
+ .modal-close {
423
+ background: none;
424
+ border: none;
425
+ font-size: 1.5rem;
426
+ cursor: pointer;
427
+ color: var(--muted-color);
428
+ }
429
+
430
+ .modal-body {
431
+ padding: 1.5rem;
432
+ }
433
+
434
+ .form-actions {
435
+ margin-top: 1.5rem;
436
+ padding-top: 1rem;
437
+ border-top: 1px solid var(--border-color);
438
+ display: flex;
439
+ gap: 0.5rem;
440
+ }
441
+
442
+ /* Alerts */
443
+ .alert {
444
+ padding: 0.75rem 1rem;
445
+ margin-bottom: 1rem;
446
+ border: 1px solid transparent;
447
+ border-radius: 0.375rem;
448
+ position: relative;
449
+ }
450
+
451
+ .alert-success {
452
+ color: #155724;
453
+ background-color: #d4edda;
454
+ border-color: #c3e6cb;
455
+ }
456
+
457
+ .alert-danger {
458
+ color: #721c24;
459
+ background-color: #f8d7da;
460
+ border-color: #f5c6cb;
461
+ }
462
+
463
+ .alert-close {
464
+ position: absolute;
465
+ top: 0.5rem;
466
+ right: 1rem;
467
+ background: none;
468
+ border: none;
469
+ font-size: 1.25rem;
470
+ cursor: pointer;
471
+ opacity: 0.5;
472
+ }
473
+
474
+ .alert-close:hover {
475
+ opacity: 1;
476
+ }
477
+
478
+ /* Empty state */
479
+ .empty-state {
480
+ text-align: center;
481
+ padding: 3rem 1rem;
482
+ color: var(--muted-color);
483
+ }
484
+
485
+ .empty-state h2 {
486
+ margin-bottom: 1rem;
487
+ }
488
+
489
+ .code-example {
490
+ background: var(--dark-color);
491
+ color: white;
492
+ padding: 1rem;
493
+ border-radius: 0.375rem;
494
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
495
+ font-size: 0.875rem;
496
+ text-align: left;
497
+ margin-top: 1rem;
498
+ overflow-x: auto;
499
+ }
500
+
501
+ /* Field display helpers */
502
+ .null-value {
503
+ color: var(--muted-color);
504
+ font-style: italic;
505
+ }
506
+
507
+ .boolean-value.true {
508
+ color: var(--success-color);
509
+ font-weight: bold;
510
+ }
511
+
512
+ .boolean-value.false {
513
+ color: var(--danger-color);
514
+ font-weight: bold;
515
+ }
516
+
517
+ .thumbnail {
518
+ border-radius: 4px;
519
+ border: 1px solid var(--border-color);
520
+ }
521
+
522
+ .file-link {
523
+ color: var(--primary-color);
524
+ text-decoration: none;
525
+ }
526
+
527
+ .file-link:hover {
528
+ text-decoration: underline;
529
+ }
530
+
531
+ .truncated-text {
532
+ position: relative;
533
+ }
534
+
535
+ .attachments {
536
+ display: flex;
537
+ gap: 0.5rem;
538
+ flex-wrap: wrap;
539
+ }
540
+
541
+ .detail-row {
542
+ display: flex;
543
+ padding: 0.75rem 0;
544
+ border-bottom: 1px solid var(--border-color);
545
+ }
546
+
547
+ .detail-label {
548
+ font-weight: 600;
549
+ min-width: 150px;
550
+ color: var(--muted-color);
551
+ }
552
+
553
+ .detail-value {
554
+ flex: 1;
555
+ }
556
+
557
+ .detail-actions {
558
+ margin-top: 1.5rem;
559
+ padding-top: 1rem;
560
+ border-top: 1px solid var(--border-color);
561
+ display: flex;
562
+ gap: 0.5rem;
563
+ }
564
+
565
+ .form-errors {
566
+ background-color: #f8d7da;
567
+ border: 1px solid #f5c6cb;
568
+ color: #721c24;
569
+ padding: 1rem;
570
+ border-radius: 0.375rem;
571
+ margin-bottom: 1rem;
572
+ }
573
+
574
+ .form-errors h4 {
575
+ margin: 0 0 0.5rem 0;
576
+ }
577
+
578
+ .form-errors ul {
579
+ margin: 0;
580
+ padding-left: 1.5rem;
581
+ }
582
+
583
+ /* Responsive */
584
+ @media (max-width: 768px) {
585
+ .quick-admin-nav {
586
+ transform: translateX(-100%);
587
+ transition: transform 0.3s;
588
+ }
589
+
590
+ .quick-admin-main {
591
+ margin-left: 0;
592
+ padding: 1rem;
593
+ }
594
+
595
+ .dashboard-stats {
596
+ grid-template-columns: 1fr;
597
+ }
598
+
599
+ .page-header {
600
+ flex-direction: column;
601
+ align-items: stretch;
602
+ gap: 1rem;
603
+ }
604
+
605
+ .search-form {
606
+ flex-direction: column;
607
+ }
608
+
609
+ .resources-table {
610
+ font-size: 0.875rem;
611
+ }
612
+
613
+ .actions .btn {
614
+ padding: 0.25rem 0.5rem;
615
+ font-size: 0.75rem;
616
+ }
617
+ }
@@ -0,0 +1,34 @@
1
+ module QuickAdmin
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ layout 'quick_admin/application'
6
+
7
+ before_action :authenticate_admin_user!, if: :authentication_required?
8
+ before_action :set_resources
9
+
10
+ private
11
+
12
+ def authenticate_admin_user!
13
+ if QuickAdmin.config.devise_enabled?
14
+ authenticate_user!
15
+ elsif QuickAdmin.config.authentication == :custom
16
+ # Override this method in your application
17
+ head :unauthorized unless admin_authenticated?
18
+ end
19
+ end
20
+
21
+ def authentication_required?
22
+ QuickAdmin.config.authentication.present?
23
+ end
24
+
25
+ def admin_authenticated?
26
+ # Override this method for custom authentication
27
+ true
28
+ end
29
+
30
+ def set_resources
31
+ @quick_admin_resources = QuickAdmin.resources
32
+ end
33
+ end
34
+ end