recycle_bin 1.1.1 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/README.md +136 -7
- data/app/controllers/recycle_bin/trash_controller.rb +418 -21
- data/app/helpers/recycle_bin/application_helper.rb +175 -0
- data/app/views/recycle_bin/layouts/recycle_bin.html.erb +33 -32
- data/app/views/recycle_bin/trash/_action_history.html.erb +13 -9
- data/app/views/recycle_bin/trash/_associations.html.erb +9 -5
- data/app/views/recycle_bin/trash/_filters.html.erb +558 -14
- data/app/views/recycle_bin/trash/_item.html.erb +262 -19
- data/app/views/recycle_bin/trash/_stats.html.erb +27 -9
- data/app/views/recycle_bin/trash/dashboard.html.erb +618 -0
- data/app/views/recycle_bin/trash/index.html.erb +246 -17
- data/config/routes.rb +9 -2
- data/lib/recycle_bin/version.rb +1 -1
- data/lib/recycle_bin.rb +111 -1
- metadata +7 -4
- data/recycle_bin.gemspec +0 -47
|
@@ -1,24 +1,267 @@
|
|
|
1
|
-
<tr>
|
|
2
|
-
<td
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<tr class="item-row" data-item-id="<%= item.id %>" data-model-type="<%= item.class.name %>">
|
|
2
|
+
<td class="checkbox-column">
|
|
3
|
+
<input type="checkbox"
|
|
4
|
+
class="item-checkbox"
|
|
5
|
+
value="<%= item.class.name %>:<%= item.id %>"
|
|
6
|
+
onchange="updateBulkCount()">
|
|
7
|
+
</td>
|
|
8
|
+
|
|
9
|
+
<td class="type-column">
|
|
10
|
+
<span class="model-badge <%= item.class.name.downcase %>">
|
|
11
|
+
<%= item.class.name %>
|
|
12
|
+
</span>
|
|
13
|
+
</td>
|
|
14
|
+
|
|
15
|
+
<td class="item-column">
|
|
16
|
+
<div class="item-content">
|
|
17
|
+
<div class="item-title">
|
|
18
|
+
<% if params[:search].present? %>
|
|
19
|
+
<%= highlight_search_terms(item.recyclable_title, params[:search]) %>
|
|
20
|
+
<% else %>
|
|
21
|
+
<%= item.recyclable_title %>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="item-meta">
|
|
25
|
+
<span class="item-id">ID: <%= item.id %></span>
|
|
26
|
+
<% if item.respond_to?(:user_id) && item.user_id.present? %>
|
|
27
|
+
<span class="item-user">User: <%= item.user_id %></span>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% if item.respond_to?(:email) && item.email.present? %>
|
|
30
|
+
<span class="item-email">
|
|
31
|
+
<% if params[:search].present? %>
|
|
32
|
+
<%= highlight_search_terms(item.email, params[:search]) %>
|
|
33
|
+
<% else %>
|
|
34
|
+
<%= item.email %>
|
|
35
|
+
<% end %>
|
|
36
|
+
</span>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</td>
|
|
41
|
+
|
|
42
|
+
<td class="timestamp-column">
|
|
43
|
+
<div class="timestamp-content">
|
|
44
|
+
<div class="timestamp-relative">
|
|
45
|
+
<%= time_ago_in_words(item.deleted_at) %> ago
|
|
46
|
+
</div>
|
|
47
|
+
<div class="timestamp-absolute">
|
|
48
|
+
<%= item.deleted_at.strftime('%B %d, %Y at %l:%M %p') %>
|
|
49
|
+
</div>
|
|
7
50
|
</div>
|
|
8
|
-
<div class="timestamp">ID: <%= item.id %></div>
|
|
9
51
|
</td>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
52
|
+
|
|
53
|
+
<td class="size-column">
|
|
54
|
+
<span class="size-badge <%= size_class(item) %>">
|
|
55
|
+
<%= human_file_size(calculate_item_memory_size(item)) %>
|
|
56
|
+
</span>
|
|
13
57
|
</td>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
58
|
+
|
|
59
|
+
<td class="actions-column">
|
|
60
|
+
<div class="action-buttons">
|
|
61
|
+
<%= link_to "👁️ View", recycle_bin.trash_path(item.class.name, item),
|
|
62
|
+
class: "btn btn-sm btn-outline",
|
|
63
|
+
title: "View details" %>
|
|
64
|
+
<%= link_to "↶ Restore", recycle_bin.restore_trash_path(item.class.name, item),
|
|
65
|
+
method: :patch,
|
|
66
|
+
class: "btn btn-sm btn-success",
|
|
67
|
+
title: "Restore item",
|
|
68
|
+
data: { confirm: "Restore this #{item.class.name.downcase}?" } %>
|
|
69
|
+
<%= link_to "🗑️ Delete", recycle_bin.destroy_trash_path(item.class.name, item),
|
|
70
|
+
method: :delete,
|
|
71
|
+
class: "btn btn-sm btn-danger",
|
|
72
|
+
title: "Permanently delete",
|
|
73
|
+
data: { confirm: "Permanently delete this #{item.class.name.downcase}? This cannot be undone!" } %>
|
|
22
74
|
</div>
|
|
23
75
|
</td>
|
|
24
|
-
</tr>
|
|
76
|
+
</tr>
|
|
77
|
+
|
|
78
|
+
<style>
|
|
79
|
+
.item-row {
|
|
80
|
+
transition: background-color 0.2s ease;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.item-row:hover {
|
|
84
|
+
background-color: #f8f9fa;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.item-content {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
gap: 4px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.item-title {
|
|
94
|
+
font-weight: 600;
|
|
95
|
+
color: #212529;
|
|
96
|
+
line-height: 1.3;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.item-meta {
|
|
100
|
+
display: flex;
|
|
101
|
+
gap: 12px;
|
|
102
|
+
flex-wrap: wrap;
|
|
103
|
+
font-size: 0.85rem;
|
|
104
|
+
color: #6c757d;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.item-id, .item-user, .item-email {
|
|
108
|
+
background: #f8f9fa;
|
|
109
|
+
padding: 2px 6px;
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
border: 1px solid #e9ecef;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.timestamp-content {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-direction: column;
|
|
117
|
+
gap: 2px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.timestamp-relative {
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
color: #495057;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.timestamp-absolute {
|
|
126
|
+
font-size: 0.8rem;
|
|
127
|
+
color: #6c757d;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.size-column {
|
|
131
|
+
text-align: center;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.size-badge {
|
|
135
|
+
padding: 4px 8px;
|
|
136
|
+
border-radius: 12px;
|
|
137
|
+
font-size: 0.8rem;
|
|
138
|
+
font-weight: 600;
|
|
139
|
+
text-align: center;
|
|
140
|
+
min-width: 60px;
|
|
141
|
+
display: inline-block;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.size-badge.small {
|
|
145
|
+
background: #d4edda;
|
|
146
|
+
color: #155724;
|
|
147
|
+
border: 1px solid #c3e6cb;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.size-badge.medium {
|
|
151
|
+
background: #fff3cd;
|
|
152
|
+
color: #856404;
|
|
153
|
+
border: 1px solid #ffeaa7;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.size-badge.large {
|
|
157
|
+
background: #f8d7da;
|
|
158
|
+
color: #721c24;
|
|
159
|
+
border: 1px solid #f5c6cb;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.action-buttons {
|
|
163
|
+
display: flex;
|
|
164
|
+
gap: 4px;
|
|
165
|
+
flex-wrap: wrap;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.action-buttons .btn {
|
|
169
|
+
font-size: 0.8rem;
|
|
170
|
+
padding: 4px 8px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Search highlighting */
|
|
174
|
+
mark {
|
|
175
|
+
background: #fff3cd;
|
|
176
|
+
color: #856404;
|
|
177
|
+
padding: 1px 3px;
|
|
178
|
+
border-radius: 3px;
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Model type badges */
|
|
183
|
+
.model-badge {
|
|
184
|
+
padding: 4px 8px;
|
|
185
|
+
border-radius: 12px;
|
|
186
|
+
font-size: 0.75rem;
|
|
187
|
+
font-weight: 600;
|
|
188
|
+
text-transform: uppercase;
|
|
189
|
+
letter-spacing: 0.5px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.model-badge.user {
|
|
193
|
+
background: #e3f2fd;
|
|
194
|
+
color: #1976d2;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.model-badge.post {
|
|
198
|
+
background: #f3e5f5;
|
|
199
|
+
color: #7b1fa2;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.model-badge.comment {
|
|
203
|
+
background: #e8f5e8;
|
|
204
|
+
color: #388e3c;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.model-badge.order {
|
|
208
|
+
background: #fff3e0;
|
|
209
|
+
color: #f57c00;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.model-badge.product {
|
|
213
|
+
background: #fce4ec;
|
|
214
|
+
color: #c2185b;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Responsive adjustments */
|
|
218
|
+
@media (max-width: 768px) {
|
|
219
|
+
.item-meta {
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
gap: 4px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.action-buttons {
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
gap: 2px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.action-buttons .btn {
|
|
230
|
+
width: 100%;
|
|
231
|
+
text-align: center;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
</style>
|
|
235
|
+
|
|
236
|
+
<script>
|
|
237
|
+
// Helper function to determine size class for styling
|
|
238
|
+
function sizeClass(item) {
|
|
239
|
+
// This will be handled by the helper method in the controller
|
|
240
|
+
return 'medium'; // Default fallback
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Update bulk count when checkboxes change
|
|
244
|
+
function updateBulkCount() {
|
|
245
|
+
const checkboxes = document.querySelectorAll('.item-checkbox:checked');
|
|
246
|
+
const bulkCount = document.getElementById('bulk-count');
|
|
247
|
+
const bulkActions = document.getElementById('bulk-actions');
|
|
248
|
+
|
|
249
|
+
if (bulkCount) {
|
|
250
|
+
bulkCount.textContent = `${checkboxes.length} item${checkboxes.length === 1 ? '' : 's'} selected`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (bulkActions) {
|
|
254
|
+
bulkActions.style.display = checkboxes.length > 0 ? 'flex' : 'none';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Update bulk action forms
|
|
258
|
+
const selectedItems = Array.from(checkboxes).map(cb => cb.value);
|
|
259
|
+
document.getElementById('bulk-restore-items').value = JSON.stringify(selectedItems);
|
|
260
|
+
document.getElementById('bulk-destroy-items').value = JSON.stringify(selectedItems);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Initialize bulk count on page load
|
|
264
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
265
|
+
updateBulkCount();
|
|
266
|
+
});
|
|
267
|
+
</script>
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
<div class="stat-number"><%= number_with_delimiter(total_count) %></div>
|
|
4
4
|
<div class="stat-label">Total Items in Trash</div>
|
|
5
5
|
<div class="stat-change positive">
|
|
6
|
-
|
|
6
|
+
<% if defined?(dashboard_view) && dashboard_view %>
|
|
7
|
+
<% today_count = filtered_items.select { |item| item.respond_to?(:deleted_at) && item.deleted_at && item.deleted_at > 1.day.ago }.count %>
|
|
8
|
+
<% else %>
|
|
9
|
+
<% today_count = filtered_items.items.select { |item| item.respond_to?(:deleted_at) && item.deleted_at && item.deleted_at > 1.day.ago }.count %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<%= today_count %> deleted today
|
|
7
12
|
</div>
|
|
8
13
|
</div>
|
|
9
14
|
<div class="stat-card">
|
|
@@ -11,22 +16,35 @@
|
|
|
11
16
|
<div class="stat-label">Model Types</div>
|
|
12
17
|
<div class="stat-change">
|
|
13
18
|
<% if model_types.any? %>
|
|
14
|
-
<%= model_types.join(', ') %>
|
|
19
|
+
<%= model_types.first(2).join(', ') %><%= model_types.count > 2 ? " +#{model_types.count - 2}" : "" %>
|
|
15
20
|
<% else %>
|
|
16
21
|
No types
|
|
17
22
|
<% end %>
|
|
18
23
|
</div>
|
|
19
24
|
</div>
|
|
20
25
|
<div class="stat-card">
|
|
21
|
-
<div class="stat-number"
|
|
26
|
+
<div class="stat-number">
|
|
27
|
+
<% if defined?(dashboard_view) && dashboard_view %>
|
|
28
|
+
<% week_count = filtered_items.select { |item| item.respond_to?(:deleted_at) && item.deleted_at && item.deleted_at > 7.days.ago }.count %>
|
|
29
|
+
<% else %>
|
|
30
|
+
<% week_count = filtered_items.items.select { |item| item.respond_to?(:deleted_at) && item.deleted_at && item.deleted_at > 7.days.ago }.count %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<%= week_count %>
|
|
33
|
+
</div>
|
|
22
34
|
<div class="stat-label">This Week</div>
|
|
23
35
|
<div class="stat-change">Recent activity</div>
|
|
24
36
|
</div>
|
|
25
37
|
<div class="stat-card">
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
<% if defined?(dashboard_view) && dashboard_view %>
|
|
39
|
+
<div class="stat-number">🏠</div>
|
|
40
|
+
<div class="stat-label">Dashboard View</div>
|
|
41
|
+
<div class="stat-change">Overview</div>
|
|
42
|
+
<% else %>
|
|
43
|
+
<div class="stat-number"><%= current_page %> / <%= defined?(total_pages) ? total_pages : 1 %></div>
|
|
44
|
+
<div class="stat-label">Current Page</div>
|
|
45
|
+
<div class="stat-change">
|
|
46
|
+
Showing <%= (current_page - 1) * per_page + 1 %>-<%= [(current_page * per_page), total_count].min %> of <%= number_with_delimiter(total_count) %>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
31
49
|
</div>
|
|
32
|
-
</div>
|
|
50
|
+
</div>
|