ragdoll-rails 0.1.9 → 0.1.11

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/ragdoll/application.js +129 -0
  3. data/app/assets/javascripts/ragdoll/bulk_upload_status.js +454 -0
  4. data/app/assets/stylesheets/ragdoll/application.css +84 -0
  5. data/app/assets/stylesheets/ragdoll/bulk_upload_status.css +379 -0
  6. data/app/channels/application_cable/channel.rb +6 -0
  7. data/app/channels/application_cable/connection.rb +6 -0
  8. data/app/channels/ragdoll/bulk_upload_status_channel.rb +27 -0
  9. data/app/channels/ragdoll/file_processing_channel.rb +26 -0
  10. data/app/components/ragdoll/alert_component.html.erb +4 -0
  11. data/app/components/ragdoll/alert_component.rb +32 -0
  12. data/app/components/ragdoll/application_component.rb +6 -0
  13. data/app/components/ragdoll/card_component.html.erb +15 -0
  14. data/app/components/ragdoll/card_component.rb +21 -0
  15. data/app/components/ragdoll/document_list_component.html.erb +41 -0
  16. data/app/components/ragdoll/document_list_component.rb +13 -0
  17. data/app/components/ragdoll/document_table_component.html.erb +76 -0
  18. data/app/components/ragdoll/document_table_component.rb +13 -0
  19. data/app/components/ragdoll/empty_state_component.html.erb +12 -0
  20. data/app/components/ragdoll/empty_state_component.rb +17 -0
  21. data/app/components/ragdoll/flash_messages_component.html.erb +3 -0
  22. data/app/components/ragdoll/flash_messages_component.rb +37 -0
  23. data/app/components/ragdoll/navbar_component.html.erb +24 -0
  24. data/app/components/ragdoll/navbar_component.rb +31 -0
  25. data/app/components/ragdoll/page_header_component.html.erb +13 -0
  26. data/app/components/ragdoll/page_header_component.rb +15 -0
  27. data/app/components/ragdoll/stats_card_component.html.erb +11 -0
  28. data/app/components/ragdoll/stats_card_component.rb +17 -0
  29. data/app/components/ragdoll/status_badge_component.html.erb +3 -0
  30. data/app/components/ragdoll/status_badge_component.rb +30 -0
  31. data/app/controllers/ragdoll/api/v1/analytics_controller.rb +72 -0
  32. data/app/controllers/ragdoll/api/v1/base_controller.rb +29 -0
  33. data/app/controllers/ragdoll/api/v1/documents_controller.rb +148 -0
  34. data/app/controllers/ragdoll/api/v1/search_controller.rb +87 -0
  35. data/app/controllers/ragdoll/api/v1/system_controller.rb +97 -0
  36. data/app/controllers/ragdoll/application_controller.rb +17 -0
  37. data/app/controllers/ragdoll/configuration_controller.rb +82 -0
  38. data/app/controllers/ragdoll/dashboard_controller.rb +98 -0
  39. data/app/controllers/ragdoll/documents_controller.rb +460 -0
  40. data/app/controllers/ragdoll/documents_controller_backup.rb +68 -0
  41. data/app/controllers/ragdoll/jobs_controller.rb +116 -0
  42. data/app/controllers/ragdoll/search_controller.rb +368 -0
  43. data/app/jobs/application_job.rb +9 -0
  44. data/app/jobs/ragdoll/bulk_document_processing_job.rb +280 -0
  45. data/app/jobs/ragdoll/process_file_job.rb +166 -0
  46. data/app/services/ragdoll/worker_health_service.rb +111 -0
  47. data/app/views/layouts/ragdoll/application.html.erb +162 -0
  48. data/app/views/ragdoll/dashboard/analytics.html.erb +333 -0
  49. data/app/views/ragdoll/dashboard/index.html.erb +208 -0
  50. data/app/views/ragdoll/documents/edit.html.erb +91 -0
  51. data/app/views/ragdoll/documents/index.html.erb +302 -0
  52. data/app/views/ragdoll/documents/new.html.erb +1518 -0
  53. data/app/views/ragdoll/documents/show.html.erb +188 -0
  54. data/app/views/ragdoll/documents/upload_results.html.erb +248 -0
  55. data/app/views/ragdoll/jobs/index.html.erb +669 -0
  56. data/app/views/ragdoll/jobs/show.html.erb +129 -0
  57. data/app/views/ragdoll/search/index.html.erb +324 -0
  58. data/config/cable.yml +12 -0
  59. data/config/routes.rb +56 -1
  60. data/lib/ragdoll/rails/engine.rb +32 -1
  61. data/lib/ragdoll/rails/version.rb +1 -1
  62. metadata +86 -1
@@ -0,0 +1,379 @@
1
+ /* Bulk Upload Status Popup Styles */
2
+ .bulk-upload-status-container {
3
+ position: fixed;
4
+ top: 20px;
5
+ right: 20px;
6
+ width: 400px;
7
+ max-width: 90vw;
8
+ max-height: 80vh;
9
+ background: #ffffff;
10
+ border: 1px solid #e1e5e9;
11
+ border-radius: 12px;
12
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
13
+ z-index: 10000;
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
15
+ font-size: 14px;
16
+ opacity: 0;
17
+ transform: translateY(-20px) scale(0.95);
18
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
19
+ pointer-events: none;
20
+ overflow: hidden;
21
+ }
22
+
23
+ .bulk-upload-status-container.visible {
24
+ opacity: 1;
25
+ transform: translateY(0) scale(1);
26
+ pointer-events: auto;
27
+ }
28
+
29
+ .bulk-upload-status-container.minimized .bulk-upload-status-content {
30
+ display: none;
31
+ }
32
+
33
+ .bulk-upload-status-container.minimized {
34
+ height: auto;
35
+ }
36
+
37
+ /* Header */
38
+ .bulk-upload-status-header {
39
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
40
+ color: white;
41
+ padding: 16px 20px;
42
+ display: flex;
43
+ justify-content: space-between;
44
+ align-items: center;
45
+ cursor: grab;
46
+ user-select: none;
47
+ }
48
+
49
+ .bulk-upload-status-header:active {
50
+ cursor: grabbing;
51
+ }
52
+
53
+ .bulk-upload-status-title {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ font-weight: 600;
58
+ font-size: 16px;
59
+ }
60
+
61
+ .bulk-upload-status-title i {
62
+ font-size: 18px;
63
+ }
64
+
65
+ .bulk-upload-status-controls {
66
+ display: flex;
67
+ gap: 8px;
68
+ }
69
+
70
+ .bulk-upload-status-controls button {
71
+ background: rgba(255, 255, 255, 0.2);
72
+ border: none;
73
+ color: white;
74
+ width: 32px;
75
+ height: 32px;
76
+ border-radius: 6px;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ cursor: pointer;
81
+ transition: background-color 0.2s ease;
82
+ }
83
+
84
+ .bulk-upload-status-controls button:hover {
85
+ background: rgba(255, 255, 255, 0.3);
86
+ }
87
+
88
+ .bulk-upload-status-controls button:active {
89
+ background: rgba(255, 255, 255, 0.4);
90
+ }
91
+
92
+ /* Content */
93
+ .bulk-upload-status-content {
94
+ max-height: 500px;
95
+ overflow-y: auto;
96
+ padding: 0;
97
+ }
98
+
99
+ .no-uploads-message {
100
+ padding: 24px 20px;
101
+ text-align: center;
102
+ color: #6b7280;
103
+ font-style: italic;
104
+ }
105
+
106
+ /* Upload Items */
107
+ .upload-item {
108
+ border-bottom: 1px solid #f3f4f6;
109
+ padding: 20px;
110
+ background: #ffffff;
111
+ transition: background-color 0.2s ease;
112
+ }
113
+
114
+ .upload-item:last-child {
115
+ border-bottom: none;
116
+ }
117
+
118
+ .upload-item:hover {
119
+ background: #f9fafb;
120
+ }
121
+
122
+ .upload-item.status-starting {
123
+ background: #fef3c7;
124
+ }
125
+
126
+ .upload-item.status-processing {
127
+ background: #dbeafe;
128
+ }
129
+
130
+ .upload-item.status-completed {
131
+ background: #d1fae5;
132
+ }
133
+
134
+ .upload-item.status-failed {
135
+ background: #fee2e2;
136
+ }
137
+
138
+ /* Upload Header */
139
+ .upload-header {
140
+ display: flex;
141
+ justify-content: space-between;
142
+ align-items: center;
143
+ margin-bottom: 12px;
144
+ }
145
+
146
+ .upload-title {
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 8px;
150
+ font-weight: 600;
151
+ color: #374151;
152
+ }
153
+
154
+ .upload-percentage {
155
+ font-weight: 700;
156
+ color: #667eea;
157
+ font-size: 16px;
158
+ }
159
+
160
+ /* Progress Bar */
161
+ .progress-bar {
162
+ width: 100%;
163
+ height: 8px;
164
+ background: #e5e7eb;
165
+ border-radius: 4px;
166
+ overflow: hidden;
167
+ margin-bottom: 16px;
168
+ }
169
+
170
+ .progress-fill {
171
+ height: 100%;
172
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
173
+ transition: width 0.3s ease;
174
+ border-radius: 4px;
175
+ }
176
+
177
+ .status-failed .progress-fill {
178
+ background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
179
+ }
180
+
181
+ .status-completed .progress-fill {
182
+ background: linear-gradient(90deg, #10b981 0%, #059669 100%);
183
+ }
184
+
185
+ /* Upload Details */
186
+ .upload-details {
187
+ space-y: 12px;
188
+ }
189
+
190
+ .upload-stats {
191
+ display: flex;
192
+ gap: 16px;
193
+ flex-wrap: wrap;
194
+ margin-bottom: 12px;
195
+ }
196
+
197
+ .upload-stats > span {
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 4px;
201
+ font-size: 13px;
202
+ color: #6b7280;
203
+ }
204
+
205
+ .processed-count {
206
+ color: #059669 !important;
207
+ font-weight: 600;
208
+ }
209
+
210
+ .failed-count {
211
+ color: #dc2626 !important;
212
+ font-weight: 600;
213
+ }
214
+
215
+ .eta {
216
+ color: #667eea !important;
217
+ font-weight: 500;
218
+ }
219
+
220
+ /* Current File */
221
+ .current-file {
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 8px;
225
+ padding: 8px 12px;
226
+ background: rgba(102, 126, 234, 0.1);
227
+ border-radius: 6px;
228
+ margin-bottom: 12px;
229
+ font-size: 13px;
230
+ color: #4338ca;
231
+ }
232
+
233
+ .current-file i {
234
+ color: #667eea;
235
+ }
236
+
237
+ /* Messages */
238
+ .completion-message,
239
+ .error-message {
240
+ display: flex;
241
+ align-items: center;
242
+ gap: 8px;
243
+ padding: 12px;
244
+ border-radius: 6px;
245
+ font-size: 13px;
246
+ font-weight: 500;
247
+ margin-bottom: 12px;
248
+ }
249
+
250
+ .completion-message {
251
+ background: #d1fae5;
252
+ color: #047857;
253
+ border: 1px solid #a7f3d0;
254
+ }
255
+
256
+ .error-message {
257
+ background: #fee2e2;
258
+ color: #dc2626;
259
+ border: 1px solid #fca5a5;
260
+ }
261
+
262
+ /* Error List */
263
+ .error-list {
264
+ margin-top: 12px;
265
+ }
266
+
267
+ .error-list details {
268
+ background: #fef2f2;
269
+ border: 1px solid #fecaca;
270
+ border-radius: 6px;
271
+ padding: 8px 12px;
272
+ }
273
+
274
+ .error-list summary {
275
+ font-weight: 600;
276
+ color: #dc2626;
277
+ cursor: pointer;
278
+ font-size: 13px;
279
+ }
280
+
281
+ .error-list summary:hover {
282
+ color: #b91c1c;
283
+ }
284
+
285
+ .error-list ul {
286
+ margin: 8px 0 0 0;
287
+ padding-left: 16px;
288
+ list-style-type: disc;
289
+ }
290
+
291
+ .error-list li {
292
+ color: #7f1d1d;
293
+ font-size: 12px;
294
+ margin: 4px 0;
295
+ word-break: break-word;
296
+ }
297
+
298
+ /* Animations */
299
+ @keyframes pulse {
300
+ 0%, 100% {
301
+ opacity: 1;
302
+ }
303
+ 50% {
304
+ opacity: 0.7;
305
+ }
306
+ }
307
+
308
+ .status-processing .upload-title i {
309
+ animation: pulse 2s infinite;
310
+ }
311
+
312
+ /* Responsive Design */
313
+ @media (max-width: 768px) {
314
+ .bulk-upload-status-container {
315
+ width: 320px;
316
+ right: 10px;
317
+ top: 10px;
318
+ }
319
+
320
+ .upload-stats {
321
+ flex-direction: column;
322
+ gap: 8px;
323
+ }
324
+
325
+ .bulk-upload-status-header {
326
+ padding: 12px 16px;
327
+ }
328
+
329
+ .upload-item {
330
+ padding: 16px;
331
+ }
332
+ }
333
+
334
+ @media (max-width: 480px) {
335
+ .bulk-upload-status-container {
336
+ width: calc(100vw - 20px);
337
+ right: 10px;
338
+ left: 10px;
339
+ }
340
+ }
341
+
342
+ /* Dark mode support */
343
+ @media (prefers-color-scheme: dark) {
344
+ .bulk-upload-status-container {
345
+ background: #1f2937;
346
+ border-color: #374151;
347
+ color: #f9fafb;
348
+ }
349
+
350
+ .upload-item {
351
+ background: #1f2937;
352
+ border-color: #374151;
353
+ }
354
+
355
+ .upload-item:hover {
356
+ background: #374151;
357
+ }
358
+
359
+ .upload-title {
360
+ color: #f9fafb;
361
+ }
362
+
363
+ .no-uploads-message {
364
+ color: #9ca3af;
365
+ }
366
+
367
+ .progress-bar {
368
+ background: #374151;
369
+ }
370
+
371
+ .upload-stats > span {
372
+ color: #9ca3af;
373
+ }
374
+
375
+ .current-file {
376
+ background: rgba(102, 126, 234, 0.2);
377
+ color: #93c5fd;
378
+ }
379
+ }
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationCable
4
+ class Channel < ActionCable::Channel::Base
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationCable
4
+ class Connection < ActionCable::Connection::Base
5
+ end
6
+ end
@@ -0,0 +1,27 @@
1
+ module Ragdoll
2
+ class BulkUploadStatusChannel < ApplicationCable::Channel
3
+ def subscribed
4
+ session_id = params[:session_id]
5
+
6
+ if session_id.present?
7
+ stream_from "bulk_upload_status_#{session_id}"
8
+ logger.info "📡 Client subscribed to bulk upload status for session: #{session_id}"
9
+ else
10
+ reject
11
+ logger.warn "⚠️ Bulk upload status subscription rejected: missing session_id"
12
+ end
13
+ end
14
+
15
+ def unsubscribed
16
+ logger.info "📡 Client unsubscribed from bulk upload status"
17
+ end
18
+
19
+ def ping(data)
20
+ # Respond to client ping to maintain connection
21
+ transmit({
22
+ type: 'pong',
23
+ timestamp: Time.current.iso8601
24
+ })
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ragdoll
4
+ class FileProcessingChannel < ApplicationCable::Channel
5
+ def subscribed
6
+ stream_from "ragdoll_file_processing_#{params[:session_id]}"
7
+ puts "📡 Ragdoll::FileProcessingChannel subscribed to ragdoll_file_processing_#{params[:session_id]}"
8
+ logger.info "📡 Ragdoll::FileProcessingChannel subscribed to ragdoll_file_processing_#{params[:session_id]}"
9
+ end
10
+
11
+ def unsubscribed
12
+ puts "📡 Ragdoll::FileProcessingChannel unsubscribed from ragdoll_file_processing_#{params[:session_id]}"
13
+ logger.info "📡 Ragdoll::FileProcessingChannel unsubscribed from ragdoll_file_processing_#{params[:session_id]}"
14
+ end
15
+
16
+ def test_connection
17
+ puts "🏓 Received test_connection ping from session: #{params[:session_id]}"
18
+ logger.info "🏓 Received test_connection ping from session: #{params[:session_id]}"
19
+ ActionCable.server.broadcast("ragdoll_file_processing_#{params[:session_id]}", {
20
+ type: 'ping',
21
+ message: 'Connection test successful',
22
+ timestamp: Time.current.to_f
23
+ })
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ <div class="<%= alert_classes %>">
2
+ <%= message %>
3
+ <%= dismiss_button %>
4
+ </div>
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ragdoll
4
+ class AlertComponent < ApplicationComponent
5
+ def initialize(message:, type: 'info', dismissible: true, **options)
6
+ @message = message
7
+ @type = type
8
+ @dismissible = dismissible
9
+ @options = options
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :message, :type, :dismissible, :options
15
+
16
+ def alert_classes
17
+ classes = ['alert', "alert-#{type}"]
18
+ classes << 'alert-dismissible' if dismissible
19
+ classes << 'fade show' if dismissible
20
+ classes << options[:class] if options[:class]
21
+ classes.join(' ')
22
+ end
23
+
24
+ def dismiss_button
25
+ return unless dismissible
26
+
27
+ content_tag :button, type: 'button', class: 'btn-close', data: { bs_dismiss: 'alert' } do
28
+ ''
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ragdoll
4
+ class ApplicationComponent < ViewComponent::Base
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ <div class="<%= card_classes %>">
2
+ <% if title %>
3
+ <div class="card-header">
4
+ <h5>
5
+ <% if icon %>
6
+ <i class="<%= icon %>"></i>
7
+ <% end %>
8
+ <%= title %>
9
+ </h5>
10
+ </div>
11
+ <% end %>
12
+ <div class="card-body">
13
+ <%= content %>
14
+ </div>
15
+ </div>
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ragdoll
4
+ class CardComponent < ApplicationComponent
5
+ def initialize(title: nil, icon: nil, **options)
6
+ @title = title
7
+ @icon = icon
8
+ @options = options
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :title, :icon, :options
14
+
15
+ def card_classes
16
+ classes = ['card']
17
+ classes << options[:class] if options[:class]
18
+ classes.join(' ')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ <div class="mb-3 d-flex align-items-center">
2
+ <input type="checkbox" id="select-all" class="form-check-input me-2" onclick="toggleAll(this)">
3
+ <label class="form-check-label text-muted">Select All</label>
4
+ </div>
5
+ <div class="row">
6
+ <% documents.each do |document| %>
7
+ <div class="col-md-6 col-lg-4 mb-4">
8
+ <div class="card h-100">
9
+ <div class="card-header d-flex justify-content-between align-items-center">
10
+ <div class="d-flex align-items-center">
11
+ <input type="checkbox" class="document-checkbox form-check-input me-2" value="<%= document.id %>">
12
+ <h6 class="card-title mb-0"><%= document.title %></h6>
13
+ </div>
14
+ <span class="badge bg-<%= document.status == 'processed' ? 'success' : document.status == 'failed' ? 'danger' : 'warning' %>">
15
+ <%= document.status.humanize %>
16
+ </span>
17
+ </div>
18
+ <div class="card-body">
19
+ <p class="card-text text-muted small mb-2">
20
+ <i class="fas fa-file"></i> <%= document.document_type %>
21
+ </p>
22
+ <p class="card-text text-muted small mb-2">
23
+ <i class="fas fa-weight"></i> <%= number_to_human_size(document.metadata['file_size']) if document.metadata && document.metadata['file_size'] %>
24
+ </p>
25
+ <p class="card-text text-muted small">
26
+ <i class="fas fa-clock"></i> <%= time_ago_in_words(document.created_at) %> ago
27
+ </p>
28
+ </div>
29
+ <div class="card-footer bg-transparent">
30
+ <div class="btn-group w-100" role="group">
31
+ <%= link_to "View", helpers.ragdoll.document_path(document), class: "btn btn-outline-primary btn-sm" %>
32
+ <%= link_to "Edit", helpers.ragdoll.edit_document_path(document), class: "btn btn-outline-secondary btn-sm" %>
33
+ <%= link_to "Delete", helpers.ragdoll.document_path(document), method: :delete,
34
+ class: "btn btn-outline-danger btn-sm",
35
+ data: { confirm: "Are you sure you want to delete '#{document.title}'?", turbo_method: :delete } %>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <% end %>
41
+ </div>
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ragdoll
4
+ class DocumentListComponent < ApplicationComponent
5
+ def initialize(documents:)
6
+ @documents = documents
7
+ end
8
+
9
+ private
10
+
11
+ attr_reader :documents
12
+ end
13
+ end
@@ -0,0 +1,76 @@
1
+ <div class="table-responsive">
2
+ <table class="table table-hover">
3
+ <thead>
4
+ <tr>
5
+ <th width="30">
6
+ <input type="checkbox" id="select-all-table" class="form-check-input" onclick="toggleAll(this)">
7
+ </th>
8
+ <th>Title</th>
9
+ <th>Type</th>
10
+ <th>Size</th>
11
+ <th>Status</th>
12
+ <th>Created</th>
13
+ <th width="150">Actions</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <% documents.each do |document| %>
18
+ <tr>
19
+ <td>
20
+ <input type="checkbox" class="document-checkbox form-check-input" value="<%= document.id %>">
21
+ </td>
22
+ <td>
23
+ <%= link_to document.title, helpers.ragdoll.document_path(document), class: "text-decoration-none" %>
24
+ </td>
25
+ <td>
26
+ <small class="text-muted">
27
+ <i class="fas fa-file"></i> <%= document.document_type %>
28
+ </small>
29
+ </td>
30
+ <td>
31
+ <small class="text-muted">
32
+ <%= number_to_human_size(document.metadata['file_size']) if document.metadata && document.metadata['file_size'] %>
33
+ </small>
34
+ </td>
35
+ <td>
36
+ <span class="badge bg-<%= document.status == 'processed' ? 'success' : document.status == 'failed' ? 'danger' : 'warning' %>">
37
+ <%= document.status.humanize %>
38
+ </span>
39
+ </td>
40
+ <td>
41
+ <small class="text-muted">
42
+ <%= time_ago_in_words(document.created_at) %> ago
43
+ </small>
44
+ </td>
45
+ <td>
46
+ <div class="btn-group btn-group-sm" role="group">
47
+ <%= link_to helpers.ragdoll.document_path(document),
48
+ class: "btn btn-outline-primary",
49
+ title: "View",
50
+ data: { bs_toggle: "tooltip" } do %>
51
+ <i class="fas fa-eye"></i>
52
+ <% end %>
53
+ <%= link_to helpers.ragdoll.edit_document_path(document),
54
+ class: "btn btn-outline-secondary",
55
+ title: "Edit",
56
+ data: { bs_toggle: "tooltip" } do %>
57
+ <i class="fas fa-edit"></i>
58
+ <% end %>
59
+ <%= link_to helpers.ragdoll.document_path(document),
60
+ method: :delete,
61
+ class: "btn btn-outline-danger",
62
+ title: "Delete",
63
+ data: {
64
+ confirm: "Are you sure you want to delete '#{document.title}'?",
65
+ turbo_method: :delete,
66
+ bs_toggle: "tooltip"
67
+ } do %>
68
+ <i class="fas fa-trash"></i>
69
+ <% end %>
70
+ </div>
71
+ </td>
72
+ </tr>
73
+ <% end %>
74
+ </tbody>
75
+ </table>
76
+ </div>
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ragdoll
4
+ class DocumentTableComponent < ViewComponent::Base
5
+ def initialize(documents:)
6
+ @documents = documents
7
+ end
8
+
9
+ private
10
+
11
+ attr_reader :documents
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ <div class="text-center py-5">
2
+ <% if icon %>
3
+ <i class="<%= icon %> fa-4x text-muted mb-4"></i>
4
+ <% end %>
5
+
6
+ <h3 class="text-muted mb-3"><%= title %></h3>
7
+ <p class="text-muted mb-4"><%= message %></p>
8
+
9
+ <% if action_path && action_text %>
10
+ <%= link_to action_text, action_path, class: "btn btn-primary" %>
11
+ <% end %>
12
+ </div>