recycle_bin 1.0.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.
@@ -0,0 +1,266 @@
1
+
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>Recycle Bin</title>
6
+ <meta name="viewport" content="width=device-width,initial-scale=1">
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+
10
+ <style>
11
+ /* Simple, clean CSS for V1 MVP */
12
+ * {
13
+ box-sizing: border-box;
14
+ margin: 0;
15
+ padding: 0;
16
+ }
17
+
18
+ body {
19
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
20
+ line-height: 1.6;
21
+ color: #333;
22
+ background-color: #f8f9fa;
23
+ }
24
+
25
+ .container {
26
+ max-width: 1200px;
27
+ margin: 0 auto;
28
+ padding: 0 20px;
29
+ }
30
+
31
+ /* Header */
32
+ .header {
33
+ background: #fff;
34
+ border-bottom: 1px solid #dee2e6;
35
+ padding: 1rem 0;
36
+ margin-bottom: 2rem;
37
+ }
38
+
39
+ .header h1 {
40
+ color: #495057;
41
+ font-size: 1.5rem;
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 0.5rem;
45
+ }
46
+
47
+ .header .trash-icon {
48
+ font-size: 1.8rem;
49
+ }
50
+
51
+ /* Navigation */
52
+ .nav {
53
+ margin-top: 1rem;
54
+ }
55
+
56
+ .nav a {
57
+ color: #6c757d;
58
+ text-decoration: none;
59
+ margin-right: 1.5rem;
60
+ padding: 0.5rem 0;
61
+ border-bottom: 2px solid transparent;
62
+ transition: all 0.2s;
63
+ }
64
+
65
+ .nav a:hover,
66
+ .nav a.active {
67
+ color: #495057;
68
+ border-bottom-color: #007bff;
69
+ }
70
+
71
+ /* Cards */
72
+ .card {
73
+ background: #fff;
74
+ border-radius: 8px;
75
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
76
+ padding: 1.5rem;
77
+ margin-bottom: 1rem;
78
+ }
79
+
80
+ /* Buttons */
81
+ .btn {
82
+ display: inline-block;
83
+ padding: 0.5rem 1rem;
84
+ border: none;
85
+ border-radius: 4px;
86
+ text-decoration: none;
87
+ cursor: pointer;
88
+ font-size: 0.875rem;
89
+ transition: all 0.2s;
90
+ }
91
+
92
+ .btn-primary {
93
+ background: #007bff;
94
+ color: white;
95
+ }
96
+
97
+ .btn-primary:hover {
98
+ background: #0056b3;
99
+ }
100
+
101
+ .btn-success {
102
+ background: #28a745;
103
+ color: white;
104
+ }
105
+
106
+ .btn-success:hover {
107
+ background: #1e7e34;
108
+ }
109
+
110
+ .btn-danger {
111
+ background: #dc3545;
112
+ color: white;
113
+ }
114
+
115
+ .btn-danger:hover {
116
+ background: #c82333;
117
+ }
118
+
119
+ .btn-sm {
120
+ padding: 0.25rem 0.75rem;
121
+ font-size: 0.8rem;
122
+ }
123
+
124
+ /* Tables */
125
+ .table {
126
+ width: 100%;
127
+ border-collapse: collapse;
128
+ background: #fff;
129
+ border-radius: 8px;
130
+ overflow: hidden;
131
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
132
+ }
133
+
134
+ .table th,
135
+ .table td {
136
+ padding: 1rem;
137
+ text-align: left;
138
+ border-bottom: 1px solid #dee2e6;
139
+ }
140
+
141
+ .table th {
142
+ background: #f8f9fa;
143
+ font-weight: 600;
144
+ color: #495057;
145
+ }
146
+
147
+ .table tr:hover {
148
+ background: #f8f9fa;
149
+ }
150
+
151
+ /* Stats */
152
+ .stats {
153
+ display: grid;
154
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
155
+ gap: 1rem;
156
+ margin-bottom: 2rem;
157
+ }
158
+
159
+ .stat-card {
160
+ background: #fff;
161
+ padding: 1.5rem;
162
+ border-radius: 8px;
163
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
164
+ text-align: center;
165
+ }
166
+
167
+ .stat-number {
168
+ font-size: 2rem;
169
+ font-weight: bold;
170
+ color: #007bff;
171
+ }
172
+
173
+ .stat-label {
174
+ color: #6c757d;
175
+ font-size: 0.875rem;
176
+ margin-top: 0.5rem;
177
+ }
178
+
179
+ /* Empty state */
180
+ .empty-state {
181
+ text-align: center;
182
+ padding: 4rem 2rem;
183
+ color: #6c757d;
184
+ }
185
+
186
+ .empty-state-icon {
187
+ font-size: 4rem;
188
+ margin-bottom: 1rem;
189
+ }
190
+
191
+ /* Responsive */
192
+ @media (max-width: 768px) {
193
+ .container {
194
+ padding: 0 15px;
195
+ }
196
+
197
+ .table {
198
+ font-size: 0.875rem;
199
+ }
200
+
201
+ .table th,
202
+ .table td {
203
+ padding: 0.75rem 0.5rem;
204
+ }
205
+
206
+ .btn {
207
+ font-size: 0.8rem;
208
+ padding: 0.4rem 0.8rem;
209
+ }
210
+ }
211
+
212
+ /* Alerts */
213
+ .alert {
214
+ padding: 0.75rem 1rem;
215
+ margin-bottom: 1rem;
216
+ border-radius: 4px;
217
+ }
218
+
219
+ .alert-success {
220
+ background: #d4edda;
221
+ color: #155724;
222
+ border: 1px solid #c3e6cb;
223
+ }
224
+
225
+ .alert-danger {
226
+ background: #f8d7da;
227
+ color: #721c24;
228
+ border: 1px solid #f5c6cb;
229
+ }
230
+
231
+ .alert-info {
232
+ background: #d1ecf1;
233
+ color: #0c5460;
234
+ border: 1px solid #bee5eb;
235
+ }
236
+ </style>
237
+ </head>
238
+
239
+ <body>
240
+ <div class="header">
241
+ <div class="container">
242
+ <h1>
243
+ <span class="trash-icon">🗑️</span>
244
+ Recycle Bin
245
+ </h1>
246
+ <nav class="nav">
247
+ <%= link_to "All Items", recycle_bin.trash_index_path,
248
+ class: ("active" if current_page?(recycle_bin.trash_index_path)) %>
249
+ <%= link_to "Statistics", "#", class: "disabled" %>
250
+ </nav>
251
+ </div>
252
+ </div>
253
+
254
+ <div class="container">
255
+ <% if notice %>
256
+ <div class="alert alert-success"><%= notice %></div>
257
+ <% end %>
258
+
259
+ <% if alert %>
260
+ <div class="alert alert-danger"><%= alert %></div>
261
+ <% end %>
262
+
263
+ <%= yield %>
264
+ </div>
265
+ </body>
266
+ </html>
@@ -0,0 +1,133 @@
1
+ <% content_for :title, "Recycle Bin" %>
2
+
3
+ <!-- Statistics Cards -->
4
+ <div class="stats">
5
+ <div class="stat-card">
6
+ <div class="stat-number"><%= @deleted_items.count %></div>
7
+ <div class="stat-label">Items in Trash</div>
8
+ </div>
9
+
10
+ <div class="stat-card">
11
+ <div class="stat-number"><%= @model_types.count %></div>
12
+ <div class="stat-label">Different Types</div>
13
+ </div>
14
+
15
+ <div class="stat-card">
16
+ <div class="stat-number">
17
+ <%= @deleted_items.where('deleted_at > ?', 1.day.ago).count %>
18
+ </div>
19
+ <div class="stat-label">Deleted Today</div>
20
+ </div>
21
+ </div>
22
+
23
+ <!-- Main Content -->
24
+ <div class="card">
25
+ <% if @deleted_items.any? %>
26
+ <h2 style="margin-bottom: 1.5rem; color: #495057;">
27
+ Deleted Items
28
+ <span style="font-size: 0.8em; color: #6c757d;">
29
+ (<%= @deleted_items.count %> items)
30
+ </span>
31
+ </h2>
32
+
33
+ <!-- Model Type Filter -->
34
+ <div style="margin-bottom: 1.5rem;">
35
+ <strong>Filter by type:</strong>
36
+ <%= link_to "All", recycle_bin.trash_index_path,
37
+ class: "btn btn-sm #{'btn-primary' if params[:type].blank?}" %>
38
+ <% @model_types.each do |model_type| %>
39
+ <%= link_to model_type, recycle_bin.trash_index_path(type: model_type),
40
+ class: "btn btn-sm #{'btn-primary' if params[:type] == model_type}" %>
41
+ <% end %>
42
+ </div>
43
+
44
+ <!-- Items Table -->
45
+ <table class="table">
46
+ <thead>
47
+ <tr>
48
+ <th>Type</th>
49
+ <th>Title</th>
50
+ <th>Deleted At</th>
51
+ <th>Actions</th>
52
+ </tr>
53
+ </thead>
54
+ <tbody>
55
+ <% @deleted_items.each do |item| %>
56
+ <tr>
57
+ <td>
58
+ <strong><%= item.class.name %></strong>
59
+ </td>
60
+ <td>
61
+ <%= link_to truncate(item.recyclable_title, length: 50),
62
+ recycle_bin.trash_path(item),
63
+ style: "text-decoration: none; color: #007bff;" %>
64
+ </td>
65
+ <td>
66
+ <span style="color: #6c757d;">
67
+ <%= time_ago_in_words(item.deleted_at) %> ago
68
+ </span>
69
+ <br>
70
+ <small style="color: #adb5bd;">
71
+ <%= item.deleted_at.strftime('%B %d, %Y at %I:%M %p') %>
72
+ </small>
73
+ </td>
74
+ <td>
75
+ <%= link_to "View", recycle_bin.trash_path(item),
76
+ class: "btn btn-sm btn-primary" %>
77
+ <%= link_to "Restore", recycle_bin.restore_trash_path(item),
78
+ method: :patch,
79
+ class: "btn btn-sm btn-success",
80
+ data: {
81
+ confirm: "Are you sure you want to restore this #{item.class.name.downcase}?"
82
+ } %>
83
+ <%= link_to "Delete Forever", recycle_bin.trash_path(item),
84
+ method: :delete,
85
+ class: "btn btn-sm btn-danger",
86
+ data: {
87
+ confirm: "This will permanently delete this #{item.class.name.downcase}. This action cannot be undone!"
88
+ } %>
89
+ </td>
90
+ </tr>
91
+ <% end %>
92
+ </tbody>
93
+ </table>
94
+
95
+ <!-- Bulk Actions -->
96
+ <div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #dee2e6;">
97
+ <p style="color: #6c757d; font-size: 0.875rem;">
98
+ <strong>Bulk Actions:</strong>
99
+ Select items above to restore or delete multiple items at once.
100
+ <em>(Coming in v1.1)</em>
101
+ </p>
102
+ </div>
103
+
104
+ <% else %>
105
+ <!-- Empty State -->
106
+ <div class="empty-state">
107
+ <div class="empty-state-icon">🎉</div>
108
+ <h3>Your trash is empty!</h3>
109
+ <p>When you delete items from your application, they'll appear here so you can restore them if needed.</p>
110
+
111
+ <div style="margin-top: 2rem;">
112
+ <h4 style="color: #495057;">How to use RecycleBin:</h4>
113
+ <div style="text-align: left; max-width: 500px; margin: 1rem auto;">
114
+ <p><strong>1.</strong> Add <code>include RecycleBin::SoftDeletable</code> to your models</p>
115
+ <p><strong>2.</strong> Add <code>deleted_at:datetime</code> column to your tables</p>
116
+ <p><strong>3.</strong> Call <code>.destroy</code> on records - they'll come here instead of being deleted</p>
117
+ <p><strong>4.</strong> Use <code>.restore</code> to bring them back</p>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ <% end %>
122
+ </div>
123
+
124
+ <!-- Help Section -->
125
+ <div class="card" style="margin-top: 2rem; background: #f8f9fa;">
126
+ <h4 style="color: #495057; margin-bottom: 1rem;">💡 Pro Tips</h4>
127
+ <ul style="color: #6c757d; line-height: 1.8;">
128
+ <li><strong>Restore:</strong> Click "Restore" to bring items back to your application</li>
129
+ <li><strong>Permanent Delete:</strong> "Delete Forever" removes items completely (cannot be undone)</li>
130
+ <li><strong>Auto-cleanup:</strong> Items older than 30 days are automatically cleaned up</li>
131
+ <li><strong>Search:</strong> Use your browser's find (Ctrl/Cmd+F) to search this page</li>
132
+ </ul>
133
+ </div>
@@ -0,0 +1,175 @@
1
+ <% content_for :title, "#{@item.class.name} Details" %>
2
+
3
+ <!-- Breadcrumb -->
4
+ <div style="margin-bottom: 2rem;">
5
+ <%= link_to "← Back to Trash", recycle_bin.trash_index_path,
6
+ style: "color: #007bff; text-decoration: none;" %>
7
+ </div>
8
+
9
+ <!-- Item Header -->
10
+ <div class="card">
11
+ <div style="display: flex; justify-content: between; align-items: start; margin-bottom: 1.5rem;">
12
+ <div style="flex: 1;">
13
+ <h1 style="color: #495057; margin-bottom: 0.5rem;">
14
+ <span style="font-size: 0.8em; color: #6c757d;"><%= @item.class.name %></span><br>
15
+ <%= @item.recyclable_title %>
16
+ </h1>
17
+ <p style="color: #6c757d;">
18
+ Deleted <%= time_ago_in_words(@item.deleted_at) %> ago
19
+ (<%= @item.deleted_at.strftime('%B %d, %Y at %I:%M %p') %>)
20
+ </p>
21
+ </div>
22
+
23
+ <div style="flex-shrink: 0;">
24
+ <%= link_to "Restore", recycle_bin.restore_trash_path(@item),
25
+ method: :patch,
26
+ class: "btn btn-success",
27
+ style: "margin-right: 0.5rem;",
28
+ data: {
29
+ confirm: "Are you sure you want to restore this #{@item.class.name.downcase}?"
30
+ } %>
31
+ <%= link_to "Delete Forever", recycle_bin.trash_path(@item),
32
+ method: :delete,
33
+ class: "btn btn-danger",
34
+ data: {
35
+ confirm: "This will permanently delete this #{@item.class.name.downcase}. This action cannot be undone!"
36
+ } %>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- Item Status -->
41
+ <div style="background: #f8f9fa; padding: 1rem; border-radius: 4px; margin-bottom: 1.5rem;">
42
+ <strong>Status:</strong>
43
+ <span style="color: #dc3545;">🗑️ In Trash</span>
44
+
45
+ <% if @item.respond_to?(:deleted_at) && @item.deleted_at %>
46
+ <br><strong>Deleted At:</strong> <%= @item.deleted_at.strftime('%B %d, %Y at %I:%M %p') %>
47
+ <% end %>
48
+ </div>
49
+ </div>
50
+
51
+ <!-- Item Data -->
52
+ <div class="card">
53
+ <h3 style="margin-bottom: 1.5rem; color: #495057;">Original Data</h3>
54
+
55
+ <% if @original_attributes.any? %>
56
+ <table class="table">
57
+ <thead>
58
+ <tr>
59
+ <th style="width: 30%;">Field</th>
60
+ <th>Value</th>
61
+ </tr>
62
+ </thead>
63
+ <tbody>
64
+ <% @original_attributes.each do |key, value| %>
65
+ <tr>
66
+ <td><strong><%= key.humanize %></strong></td>
67
+ <td>
68
+ <% if value.nil? %>
69
+ <em style="color: #6c757d;">nil</em>
70
+ <% elsif value.is_a?(String) && value.length > 100 %>
71
+ <div>
72
+ <%= truncate(value, length: 100) %>
73
+ <details style="margin-top: 0.5rem;">
74
+ <summary style="cursor: pointer; color: #007bff;">Show full content</summary>
75
+ <div style="margin-top: 0.5rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px;">
76
+ <%= simple_format(value) %>
77
+ </div>
78
+ </details>
79
+ </div>
80
+ <% elsif value.is_a?(Time) || value.is_a?(DateTime) %>
81
+ <%= value.strftime('%B %d, %Y at %I:%M %p') %>
82
+ <br><small style="color: #6c757d;">(<%= time_ago_in_words(value) %> ago)</small>
83
+ <% elsif value.is_a?(Date) %>
84
+ <%= value.strftime('%B %d, %Y') %>
85
+ <% elsif value == true %>
86
+ <span style="color: #28a745;">✓ Yes</span>
87
+ <% elsif value == false %>
88
+ <span style="color: #dc3545;">✗ No</span>
89
+ <% else %>
90
+ <%= value %>
91
+ <% end %>
92
+ </td>
93
+ </tr>
94
+ <% end %>
95
+ </tbody>
96
+ </table>
97
+ <% else %>
98
+ <div class="empty-state">
99
+ <p>No data available for this item.</p>
100
+ </div>
101
+ <% end %>
102
+ </div>
103
+
104
+ <!-- Associations (if any) -->
105
+ <% if @associations.any? %>
106
+ <div class="card">
107
+ <h3 style="margin-bottom: 1.5rem; color: #495057;">Related Items</h3>
108
+
109
+ <% @associations.each do |association_name, items| %>
110
+ <div style="margin-bottom: 2rem;">
111
+ <h4 style="color: #6c757d; margin-bottom: 1rem;"><%= association_name.humanize %></h4>
112
+
113
+ <% if items.any? %>
114
+ <ul style="list-style: none; padding: 0;">
115
+ <% items.each do |item| %>
116
+ <li style="padding: 0.5rem; background: #f8f9fa; margin-bottom: 0.5rem; border-radius: 4px;">
117
+ <%= item.class.name %> -
118
+ <%= item.respond_to?(:title) ? item.title :
119
+ item.respond_to?(:name) ? item.name :
120
+ "ID: #{item.id}" %>
121
+
122
+ <% if item.respond_to?(:deleted_at) && item.deleted_at %>
123
+ <span style="color: #dc3545; font-size: 0.875rem;">(Also deleted)</span>
124
+ <% end %>
125
+ </li>
126
+ <% end %>
127
+ </ul>
128
+ <% else %>
129
+ <p style="color: #6c757d; font-style: italic;">No related items found.</p>
130
+ <% end %>
131
+ </div>
132
+ <% end %>
133
+ </div>
134
+ <% end %>
135
+
136
+ <!-- Actions -->
137
+ <div class="card" style="background: #f8f9fa;">
138
+ <h4 style="color: #495057; margin-bottom: 1rem;">Available Actions</h4>
139
+
140
+ <div style="display: flex; gap: 1rem; flex-wrap: wrap;">
141
+ <%= link_to recycle_bin.restore_trash_path(@item),
142
+ method: :patch,
143
+ class: "btn btn-success",
144
+ data: { confirm: "Restore this #{@item.class.name.downcase}?" } do %>
145
+ ↶ Restore Item
146
+ <% end %>
147
+
148
+ <%= link_to recycle_bin.trash_path(@item),
149
+ method: :delete,
150
+ class: "btn btn-danger",
151
+ data: { confirm: "Permanently delete this #{@item.class.name.downcase}? This cannot be undone!" } do %>
152
+ 🗑️ Delete Forever
153
+ <% end %>
154
+
155
+ <%= link_to recycle_bin.trash_index_path, class: "btn btn-primary" do %>
156
+ 📋 Back to List
157
+ <% end %>
158
+ </div>
159
+ </div>
160
+
161
+ <!-- Debug Info (only in development) -->
162
+ <% if Rails.env.development? %>
163
+ <div class="card" style="margin-top: 2rem; border: 1px dashed #dee2e6;">
164
+ <details>
165
+ <summary style="cursor: pointer; color: #6c757d;">🔍 Debug Info (Development Only)</summary>
166
+ <div style="margin-top: 1rem;">
167
+ <p><strong>Class:</strong> <%= @item.class.name %></p>
168
+ <p><strong>ID:</strong> <%= @item.id %></p>
169
+ <p><strong>Deleted At:</strong> <%= @item.deleted_at %></p>
170
+ <p><strong>Attributes:</strong></p>
171
+ <pre style="background: #f8f9fa; padding: 1rem; border-radius: 4px; overflow-x: auto;"><%= @item.attributes.to_yaml %></pre>
172
+ </div>
173
+ </details>
174
+ </div>
175
+ <% end %>