solidstats 2.0.0 → 3.0.0.beta.1

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -63
  3. data/README.md +27 -33
  4. data/Rakefile +3 -3
  5. data/app/assets/stylesheets/solidstats/application.css +1 -6
  6. data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
  7. data/app/controllers/solidstats/dashboard_controller.rb +81 -62
  8. data/app/controllers/solidstats/logs_controller.rb +72 -0
  9. data/app/controllers/solidstats/performance_controller.rb +25 -0
  10. data/app/controllers/solidstats/productivity_controller.rb +39 -0
  11. data/app/controllers/solidstats/quality_controller.rb +152 -0
  12. data/app/controllers/solidstats/securities_controller.rb +30 -0
  13. data/app/helpers/solidstats/application_helper.rb +124 -11
  14. data/app/helpers/solidstats/performance_helper.rb +87 -0
  15. data/app/helpers/solidstats/productivity_helper.rb +38 -0
  16. data/app/services/solidstats/bundler_audit_service.rb +206 -0
  17. data/app/services/solidstats/coverage_compass_service.rb +335 -0
  18. data/app/services/solidstats/load_lens_service.rb +454 -0
  19. data/app/services/solidstats/log_size_monitor_service.rb +207 -76
  20. data/app/services/solidstats/my_todo_service.rb +242 -0
  21. data/app/services/solidstats/style_patrol_service.rb +319 -0
  22. data/app/views/layouts/solidstats/application.html.erb +8 -2
  23. data/app/views/layouts/solidstats/dashboard.html.erb +84 -0
  24. data/app/views/solidstats/dashboard/dashboard.html.erb +39 -0
  25. data/app/views/solidstats/logs/logs_size.html.erb +409 -0
  26. data/app/views/solidstats/performance/load_lens.html.erb +158 -0
  27. data/app/views/solidstats/productivity/_todo_list.html.erb +49 -0
  28. data/app/views/solidstats/productivity/my_todos.html.erb +84 -0
  29. data/app/views/solidstats/quality/coverage_compass.html.erb +420 -0
  30. data/app/views/solidstats/quality/style_patrol.html.erb +463 -0
  31. data/app/views/solidstats/securities/bundler_audit.html.erb +345 -0
  32. data/app/views/solidstats/shared/_dashboard_card.html.erb +160 -0
  33. data/app/views/solidstats/shared/_quick_actions.html.erb +26 -0
  34. data/config/routes.rb +32 -7
  35. data/lib/generators/solidstats/install/install_generator.rb +28 -2
  36. data/lib/generators/solidstats/install/templates/README +7 -0
  37. data/lib/solidstats/engine.rb +9 -114
  38. data/lib/solidstats/version.rb +1 -1
  39. data/lib/solidstats.rb +2 -299
  40. data/lib/tasks/solidstats_install.rake +2 -122
  41. data/lib/tasks/solidstats_performance.rake +84 -0
  42. metadata +32 -103
  43. data/app/assets/javascripts/solidstats/application.js +0 -257
  44. data/app/assets/javascripts/solidstats/dashboard.js +0 -225
  45. data/app/assets/javascripts/solidstats/gem_metadata.js +0 -554
  46. data/app/assets/stylesheets/solidstats/components/action_button.css +0 -99
  47. data/app/assets/stylesheets/solidstats/components/dashboard.css +0 -151
  48. data/app/assets/stylesheets/solidstats/components/dashboard_header.css +0 -93
  49. data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +0 -97
  50. data/app/assets/stylesheets/solidstats/components/gem_metadata.css +0 -1403
  51. data/app/assets/stylesheets/solidstats/components/navigation.css +0 -80
  52. data/app/assets/stylesheets/solidstats/components/quick_navigation.css +0 -54
  53. data/app/assets/stylesheets/solidstats/components/security.css +0 -332
  54. data/app/assets/stylesheets/solidstats/components/status_badge.css +0 -58
  55. data/app/assets/stylesheets/solidstats/components/summary_card.css +0 -66
  56. data/app/assets/stylesheets/solidstats/components/tab_navigation.css +0 -95
  57. data/app/components/solidstats/base_component.rb +0 -88
  58. data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
  59. data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
  60. data/app/components/solidstats/code_quality/section_component.html.erb +0 -45
  61. data/app/components/solidstats/code_quality/section_component.rb +0 -34
  62. data/app/components/solidstats/dashboard_header_component.html.erb +0 -39
  63. data/app/components/solidstats/dashboard_header_component.rb +0 -33
  64. data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +0 -6
  65. data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +0 -6
  66. data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +0 -6
  67. data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +0 -6
  68. data/app/components/solidstats/previews/action_button_component_preview.rb +0 -64
  69. data/app/components/solidstats/previews/navigation_component_preview.rb +0 -74
  70. data/app/components/solidstats/previews/stats_overview_component_preview.rb +0 -100
  71. data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +0 -6
  72. data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +0 -6
  73. data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +0 -6
  74. data/app/components/solidstats/previews/status_badge_component_preview.rb +0 -49
  75. data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +0 -9
  76. data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +0 -9
  77. data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +0 -6
  78. data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +0 -6
  79. data/app/components/solidstats/previews/summary_card_component_preview.rb +0 -67
  80. data/app/components/solidstats/quick_navigation_component.html.erb +0 -8
  81. data/app/components/solidstats/quick_navigation_component.rb +0 -21
  82. data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +0 -44
  83. data/app/components/solidstats/security/gem_impact_analysis_component.rb +0 -45
  84. data/app/components/solidstats/security/overview_component.html.erb +0 -21
  85. data/app/components/solidstats/security/overview_component.rb +0 -104
  86. data/app/components/solidstats/security/section_component.html.erb +0 -26
  87. data/app/components/solidstats/security/section_component.rb +0 -52
  88. data/app/components/solidstats/security/timeline_component.html.erb +0 -39
  89. data/app/components/solidstats/security/timeline_component.rb +0 -43
  90. data/app/components/solidstats/tasks_section_component.html.erb +0 -17
  91. data/app/components/solidstats/tasks_section_component.rb +0 -22
  92. data/app/components/solidstats/ui/action_button_component.html.erb +0 -6
  93. data/app/components/solidstats/ui/action_button_component.rb +0 -71
  94. data/app/components/solidstats/ui/dashboard_layout_component.html.erb +0 -19
  95. data/app/components/solidstats/ui/dashboard_layout_component.rb +0 -85
  96. data/app/components/solidstats/ui/navigation_component.html.erb +0 -34
  97. data/app/components/solidstats/ui/navigation_component.rb +0 -72
  98. data/app/components/solidstats/ui/stats_overview_component.html.erb +0 -14
  99. data/app/components/solidstats/ui/stats_overview_component.rb +0 -78
  100. data/app/components/solidstats/ui/status_badge_component.html.erb +0 -6
  101. data/app/components/solidstats/ui/status_badge_component.rb +0 -42
  102. data/app/components/solidstats/ui/summary_card_component.html.erb +0 -12
  103. data/app/components/solidstats/ui/summary_card_component.rb +0 -63
  104. data/app/components/solidstats/ui/tab_navigation_component.html.erb +0 -22
  105. data/app/components/solidstats/ui/tab_navigation_component.rb +0 -79
  106. data/app/controllers/solidstats/gem_metadata_controller.rb +0 -12
  107. data/app/services/solidstats/audit_service.rb +0 -56
  108. data/app/services/solidstats/data_collector_service.rb +0 -83
  109. data/app/services/solidstats/gem_metadata/fetcher_service.rb +0 -136
  110. data/app/services/solidstats/todo_service.rb +0 -114
  111. data/app/views/solidstats/dashboard/_log_monitor.html.erb +0 -759
  112. data/app/views/solidstats/dashboard/_todos.html.erb +0 -151
  113. data/app/views/solidstats/dashboard/audit/_additional_styles.css +0 -22
  114. data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +0 -5
  115. data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +0 -495
  116. data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +0 -26
  117. data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +0 -3
  118. data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -14
  119. data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +0 -1120
  120. data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +0 -63
  121. data/app/views/solidstats/dashboard/index.html.erb +0 -81
  122. data/app/views/solidstats/gem_metadata/_panel.html.erb +0 -419
  123. data/lib/generators/solidstats/feature/feature_generator.rb +0 -170
  124. data/lib/generators/solidstats/feature/templates/component.html.erb +0 -84
  125. data/lib/generators/solidstats/feature/templates/component.rb.erb +0 -103
  126. data/lib/generators/solidstats/feature/templates/component.scss +0 -243
  127. data/lib/generators/solidstats/feature/templates/component_test.rb.erb +0 -183
  128. data/lib/generators/solidstats/feature/templates/controller.rb.erb +0 -44
  129. data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +0 -111
  130. data/lib/generators/solidstats/feature/templates/detail_view.html.erb +0 -755
  131. data/lib/generators/solidstats/feature/templates/preview.rb.erb +0 -107
  132. data/lib/generators/solidstats/feature/templates/service.rb.erb +0 -132
  133. data/lib/generators/solidstats/feature/templates/service_test.rb.erb +0 -109
  134. data/lib/generators/solidstats/install_generator.rb +0 -109
  135. data/lib/generators/solidstats/templates/initializer.rb +0 -112
  136. data/lib/solidstats/asset_compatibility.rb +0 -238
  137. data/lib/solidstats/asset_manifest.rb +0 -205
  138. data/lib/tasks/solidstats_tasks.rake +0 -4
@@ -1,755 +0,0 @@
1
- <%# Detail view for <%= human_name %> feature %>
2
- <div class="<%= file_name.dasherize %>-detail-view">
3
- <%# Page Header %>
4
- <div class="page-header">
5
- <div class="header-content">
6
- <div class="header-main">
7
- <h1 class="page-title">
8
- <span class="page-icon"><%= options[:icon] %></span>
9
- <%= human_name %>
10
- </h1>
11
- <div class="header-actions">
12
- <button class="action-btn refresh-btn" onclick="refreshData('<%= file_name %>')">
13
- <span class="btn-icon">🔄</span>
14
- Refresh Data
15
- </button>
16
- <a href="<%= solidstats.dashboard_path %>" class="action-btn secondary">
17
- <span class="btn-icon">🏠</span>
18
- Back to Dashboard
19
- </a>
20
- </div>
21
- </div>
22
- <div class="page-description">
23
- Detailed analysis and insights for <%= file_name.humanize.downcase %> in your application.
24
- </div>
25
- </div>
26
- </div>
27
-
28
- <%# Stats Overview %>
29
- <div class="stats-overview">
30
- <div class="stat-card total">
31
- <div class="stat-icon"><%= options[:icon] %></div>
32
- <div class="stat-content">
33
- <div class="stat-number"><%= @data[:value] || 0 %></div>
34
- <div class="stat-label">Total <%= human_name %></div>
35
- </div>
36
- </div>
37
-
38
- <% if @data[:items]&.any? %>
39
- <div class="stat-card">
40
- <div class="stat-icon">📊</div>
41
- <div class="stat-content">
42
- <div class="stat-number"><%= @data[:items].size %></div>
43
- <div class="stat-label">Items Found</div>
44
- </div>
45
- </div>
46
-
47
- <% if @data[:breakdown]&.any? %>
48
- <div class="stat-card">
49
- <div class="stat-icon">📈</div>
50
- <div class="stat-content">
51
- <div class="stat-number"><%= @data[:breakdown].keys.size %></div>
52
- <div class="stat-label">Categories</div>
53
- </div>
54
- </div>
55
- <% end %>
56
- <% end %>
57
-
58
- <div class="stat-card">
59
- <div class="stat-icon">⏰</div>
60
- <div class="stat-content">
61
- <div class="stat-number">
62
- <%= time_ago_in_words(@data[:last_updated] || Time.current) %>
63
- </div>
64
- <div class="stat-label">Last Updated</div>
65
- </div>
66
- </div>
67
- </div>
68
-
69
- <%# Main Content %>
70
- <% if @data[:items]&.any? %>
71
- <%# Breakdown by Category %>
72
- <% if @data[:breakdown]&.any? %>
73
- <div class="content-section">
74
- <h2 class="section-title">
75
- <span class="section-icon">📊</span>
76
- Breakdown by Category
77
- </h2>
78
- <div class="breakdown-grid">
79
- <% @data[:breakdown].each do |category, items| %>
80
- <div class="breakdown-card">
81
- <div class="breakdown-header">
82
- <h3 class="breakdown-title"><%= category.humanize %></h3>
83
- <span class="breakdown-count"><%= items.size %></span>
84
- </div>
85
- <div class="breakdown-items">
86
- <% items.first(3).each do |item| %>
87
- <div class="breakdown-item">
88
- <span class="item-name"><%= item[:name] %></span>
89
- <span class="item-value"><%= item[:value] %></span>
90
- </div>
91
- <% end %>
92
- <% if items.size > 3 %>
93
- <div class="breakdown-more">
94
- +<%= items.size - 3 %> more
95
- </div>
96
- <% end %>
97
- </div>
98
- </div>
99
- <% end %>
100
- </div>
101
- </div>
102
- <% end %>
103
-
104
- <%# Detailed Items Table %>
105
- <div class="content-section">
106
- <h2 class="section-title">
107
- <span class="section-icon">📋</span>
108
- Detailed Items
109
- </h2>
110
-
111
- <%# Filters and Search %>
112
- <div class="table-controls">
113
- <div class="search-group">
114
- <input type="text" id="item-search" placeholder="Search items..." class="search-input">
115
- <button class="search-btn">🔍</button>
116
- </div>
117
- <div class="filter-group">
118
- <% if @data[:breakdown]&.any? %>
119
- <select id="category-filter" class="filter-select">
120
- <option value="">All Categories</option>
121
- <% @data[:breakdown].keys.each do |category| %>
122
- <option value="<%= category %>"><%= category.humanize %></option>
123
- <% end %>
124
- </select>
125
- <% end %>
126
- <button class="reset-filters" onclick="resetFilters()">Clear Filters</button>
127
- </div>
128
- </div>
129
-
130
- <div class="table-container">
131
- <table class="items-table" id="items-table">
132
- <thead>
133
- <tr>
134
- <th class="sortable" data-sort="name">
135
- Name
136
- <span class="sort-icon">↕️</span>
137
- </th>
138
- <th class="sortable" data-sort="value">
139
- Value
140
- <span class="sort-icon">↕️</span>
141
- </th>
142
- <% if @data[:items].any? { |item| item[:category] } %>
143
- <th class="sortable" data-sort="category">
144
- Category
145
- <span class="sort-icon">↕️</span>
146
- </th>
147
- <% end %>
148
- <% if @data[:items].any? { |item| item[:created_at] } %>
149
- <th class="sortable" data-sort="created_at">
150
- Created
151
- <span class="sort-icon">↕️</span>
152
- </th>
153
- <% end %>
154
- <th>Actions</th>
155
- </tr>
156
- </thead>
157
- <tbody>
158
- <% @data[:items].each do |item| %>
159
- <tr class="item-row" data-category="<%= item[:category] %>" data-name="<%= item[:name] %>">
160
- <td class="item-name">
161
- <strong><%= item[:name] %></strong>
162
- <% if item[:description] %>
163
- <div class="item-description"><%= item[:description] %></div>
164
- <% end %>
165
- </td>
166
- <td class="item-value">
167
- <span class="value-badge"><%= item[:value] %></span>
168
- </td>
169
- <% if @data[:items].any? { |i| i[:category] } %>
170
- <td class="item-category">
171
- <% if item[:category] %>
172
- <span class="category-badge category-<%= item[:category].downcase %>">
173
- <%= item[:category].humanize %>
174
- </span>
175
- <% else %>
176
- <span class="text-muted">—</span>
177
- <% end %>
178
- </td>
179
- <% end %>
180
- <% if @data[:items].any? { |i| i[:created_at] } %>
181
- <td class="item-date">
182
- <% if item[:created_at] %>
183
- <time datetime="<%= item[:created_at].iso8601 %>">
184
- <%= time_ago_in_words(item[:created_at]) %> ago
185
- </time>
186
- <% else %>
187
- <span class="text-muted">—</span>
188
- <% end %>
189
- </td>
190
- <% end %>
191
- <td class="item-actions">
192
- <button class="action-btn small" onclick="showItemDetails('<%= item[:name] %>')">
193
- View
194
- </button>
195
- </td>
196
- </tr>
197
- <% end %>
198
- </tbody>
199
- </table>
200
- </div>
201
-
202
- <div class="table-footer">
203
- <div class="results-info">
204
- Showing <span id="visible-count"><%= @data[:items].size %></span> of <span id="total-count"><%= @data[:items].size %></span> items
205
- </div>
206
- </div>
207
- </div>
208
- <% else %>
209
- <%# Empty State %>
210
- <div class="empty-state">
211
- <div class="empty-icon">🎉</div>
212
- <div class="empty-title">No <%= file_name.humanize.downcase %> found</div>
213
- <div class="empty-description">
214
- This is good news! No issues or items were detected in this category.
215
- </div>
216
- <div class="empty-actions">
217
- <button class="action-btn primary" onclick="refreshData('<%= file_name %>')">
218
- <span class="btn-icon">🔄</span>
219
- Check Again
220
- </button>
221
- </div>
222
- </div>
223
- <% end %>
224
- </div>
225
-
226
- <%# Embedded Styles %>
227
- <style>
228
- .<%= file_name.dasherize %>-detail-view {
229
- max-width: 1200px;
230
- margin: 0 auto;
231
- padding: 2rem;
232
- background: #f8fafc;
233
- min-height: 100vh;
234
- }
235
-
236
- .page-header {
237
- background: white;
238
- border-radius: 12px;
239
- padding: 2rem;
240
- margin-bottom: 2rem;
241
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
242
- border: 1px solid rgba(0, 0, 0, 0.05);
243
- }
244
-
245
- .header-main {
246
- display: flex;
247
- justify-content: space-between;
248
- align-items: center;
249
- margin-bottom: 1rem;
250
- }
251
-
252
- .page-title {
253
- font-size: 2rem;
254
- font-weight: 700;
255
- color: #1f2937;
256
- margin: 0;
257
- display: flex;
258
- align-items: center;
259
- gap: 0.75rem;
260
- }
261
-
262
- .page-icon {
263
- font-size: 2.5rem;
264
- }
265
-
266
- .header-actions {
267
- display: flex;
268
- gap: 0.75rem;
269
- }
270
-
271
- .action-btn {
272
- display: inline-flex;
273
- align-items: center;
274
- gap: 0.5rem;
275
- padding: 0.75rem 1.25rem;
276
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
277
- color: white;
278
- text-decoration: none;
279
- border-radius: 8px;
280
- font-weight: 500;
281
- border: none;
282
- cursor: pointer;
283
- transition: all 0.2s ease;
284
- }
285
-
286
- .action-btn:hover {
287
- transform: translateY(-2px);
288
- box-shadow: 0 8px 25px -5px rgba(102, 126, 234, 0.4);
289
- }
290
-
291
- .action-btn.secondary {
292
- background: #6b7280;
293
- }
294
-
295
- .action-btn.small {
296
- padding: 0.375rem 0.75rem;
297
- font-size: 0.875rem;
298
- }
299
-
300
- .page-description {
301
- color: #6b7280;
302
- font-size: 1.125rem;
303
- line-height: 1.6;
304
- }
305
-
306
- .stats-overview {
307
- display: grid;
308
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
309
- gap: 1.5rem;
310
- margin-bottom: 2rem;
311
- }
312
-
313
- .stat-card {
314
- background: white;
315
- border-radius: 12px;
316
- padding: 1.5rem;
317
- display: flex;
318
- align-items: center;
319
- gap: 1rem;
320
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
321
- border: 1px solid rgba(0, 0, 0, 0.05);
322
- transition: transform 0.2s ease;
323
- }
324
-
325
- .stat-card:hover {
326
- transform: translateY(-2px);
327
- }
328
-
329
- .stat-card.total {
330
- border-left: 4px solid #3b82f6;
331
- }
332
-
333
- .stat-icon {
334
- font-size: 2rem;
335
- opacity: 0.8;
336
- }
337
-
338
- .stat-content {
339
- flex: 1;
340
- }
341
-
342
- .stat-number {
343
- font-size: 2rem;
344
- font-weight: 700;
345
- color: #1f2937;
346
- line-height: 1;
347
- margin-bottom: 0.25rem;
348
- }
349
-
350
- .stat-label {
351
- font-size: 0.875rem;
352
- color: #6b7280;
353
- font-weight: 500;
354
- }
355
-
356
- .content-section {
357
- background: white;
358
- border-radius: 12px;
359
- padding: 2rem;
360
- margin-bottom: 2rem;
361
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
362
- border: 1px solid rgba(0, 0, 0, 0.05);
363
- }
364
-
365
- .section-title {
366
- font-size: 1.5rem;
367
- font-weight: 600;
368
- color: #1f2937;
369
- margin: 0 0 1.5rem 0;
370
- display: flex;
371
- align-items: center;
372
- gap: 0.5rem;
373
- }
374
-
375
- .breakdown-grid {
376
- display: grid;
377
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
378
- gap: 1.5rem;
379
- }
380
-
381
- .breakdown-card {
382
- background: #f9fafb;
383
- border: 1px solid #e5e7eb;
384
- border-radius: 8px;
385
- padding: 1.5rem;
386
- }
387
-
388
- .breakdown-header {
389
- display: flex;
390
- justify-content: space-between;
391
- align-items: center;
392
- margin-bottom: 1rem;
393
- }
394
-
395
- .breakdown-title {
396
- font-size: 1.125rem;
397
- font-weight: 600;
398
- margin: 0;
399
- color: #1f2937;
400
- }
401
-
402
- .breakdown-count {
403
- background: #3b82f6;
404
- color: white;
405
- padding: 0.25rem 0.75rem;
406
- border-radius: 12px;
407
- font-size: 0.875rem;
408
- font-weight: 600;
409
- }
410
-
411
- .breakdown-item {
412
- display: flex;
413
- justify-content: space-between;
414
- padding: 0.5rem 0;
415
- border-bottom: 1px solid #e5e7eb;
416
- }
417
-
418
- .breakdown-item:last-child {
419
- border-bottom: none;
420
- }
421
-
422
- .item-name {
423
- color: #374151;
424
- font-weight: 500;
425
- }
426
-
427
- .item-value {
428
- color: #3b82f6;
429
- font-weight: 600;
430
- }
431
-
432
- .breakdown-more {
433
- color: #6b7280;
434
- font-style: italic;
435
- padding-top: 0.5rem;
436
- text-align: center;
437
- }
438
-
439
- .table-controls {
440
- display: flex;
441
- justify-content: space-between;
442
- align-items: center;
443
- margin-bottom: 1.5rem;
444
- gap: 1rem;
445
- }
446
-
447
- .search-group {
448
- display: flex;
449
- align-items: center;
450
- gap: 0.5rem;
451
- }
452
-
453
- .search-input {
454
- padding: 0.75rem 1rem;
455
- border: 1px solid #d1d5db;
456
- border-radius: 8px;
457
- font-size: 0.875rem;
458
- min-width: 200px;
459
- }
460
-
461
- .filter-group {
462
- display: flex;
463
- gap: 0.75rem;
464
- align-items: center;
465
- }
466
-
467
- .filter-select {
468
- padding: 0.75rem 1rem;
469
- border: 1px solid #d1d5db;
470
- border-radius: 8px;
471
- font-size: 0.875rem;
472
- background: white;
473
- }
474
-
475
- .table-container {
476
- overflow-x: auto;
477
- border: 1px solid #e5e7eb;
478
- border-radius: 8px;
479
- }
480
-
481
- .items-table {
482
- width: 100%;
483
- border-collapse: collapse;
484
- font-size: 0.875rem;
485
- }
486
-
487
- .items-table th {
488
- background: #f9fafb;
489
- padding: 1rem 0.75rem;
490
- text-align: left;
491
- font-weight: 600;
492
- color: #374151;
493
- border-bottom: 1px solid #e5e7eb;
494
- }
495
-
496
- .items-table th.sortable {
497
- cursor: pointer;
498
- user-select: none;
499
- }
500
-
501
- .items-table td {
502
- padding: 1rem 0.75rem;
503
- border-bottom: 1px solid #f3f4f6;
504
- }
505
-
506
- .item-row:hover {
507
- background: #f9fafb;
508
- }
509
-
510
- .item-description {
511
- font-size: 0.75rem;
512
- color: #6b7280;
513
- margin-top: 0.25rem;
514
- }
515
-
516
- .value-badge {
517
- background: #dbeafe;
518
- color: #1e40af;
519
- padding: 0.25rem 0.75rem;
520
- border-radius: 12px;
521
- font-weight: 600;
522
- }
523
-
524
- .category-badge {
525
- padding: 0.25rem 0.75rem;
526
- border-radius: 12px;
527
- font-size: 0.75rem;
528
- font-weight: 600;
529
- }
530
-
531
- .category-important {
532
- background: #fef3c7;
533
- color: #d97706;
534
- }
535
-
536
- .category-normal {
537
- background: #dbeafe;
538
- color: #1e40af;
539
- }
540
-
541
- .category-low {
542
- background: #d1fae5;
543
- color: #065f46;
544
- }
545
-
546
- .text-muted {
547
- color: #9ca3af;
548
- }
549
-
550
- .table-footer {
551
- text-align: center;
552
- padding: 1rem;
553
- background: #f9fafb;
554
- font-size: 0.875rem;
555
- color: #6b7280;
556
- }
557
-
558
- .empty-state {
559
- text-align: center;
560
- padding: 4rem 2rem;
561
- color: #6b7280;
562
- }
563
-
564
- .empty-icon {
565
- font-size: 4rem;
566
- margin-bottom: 1.5rem;
567
- opacity: 0.7;
568
- }
569
-
570
- .empty-title {
571
- font-size: 1.5rem;
572
- font-weight: 700;
573
- color: #374151;
574
- margin-bottom: 0.75rem;
575
- }
576
-
577
- .empty-description {
578
- margin-bottom: 2rem;
579
- max-width: 500px;
580
- margin-left: auto;
581
- margin-right: auto;
582
- }
583
-
584
- @media (max-width: 768px) {
585
- .<%= file_name.dasherize %>-detail-view {
586
- padding: 1rem;
587
- }
588
-
589
- .header-main {
590
- flex-direction: column;
591
- gap: 1rem;
592
- align-items: flex-start;
593
- }
594
-
595
- .stats-overview {
596
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
597
- }
598
-
599
- .table-controls {
600
- flex-direction: column;
601
- align-items: stretch;
602
- }
603
-
604
- .breakdown-grid {
605
- grid-template-columns: 1fr;
606
- }
607
- }
608
- </style>
609
-
610
- <%# JavaScript for interactions %>
611
- <script>
612
- // Table filtering and search
613
- document.addEventListener('DOMContentLoaded', function() {
614
- const searchInput = document.getElementById('item-search');
615
- const categoryFilter = document.getElementById('category-filter');
616
- const table = document.getElementById('items-table');
617
- const visibleCount = document.getElementById('visible-count');
618
- const totalCount = document.getElementById('total-count');
619
-
620
- function filterTable() {
621
- const searchTerm = searchInput?.value.toLowerCase() || '';
622
- const categoryFilter = document.getElementById('category-filter')?.value || '';
623
- const rows = table?.querySelectorAll('.item-row') || [];
624
- let visibleRows = 0;
625
-
626
- rows.forEach(row => {
627
- const name = row.dataset.name?.toLowerCase() || '';
628
- const category = row.dataset.category || '';
629
-
630
- const matchesSearch = name.includes(searchTerm);
631
- const matchesCategory = !categoryFilter || category === categoryFilter;
632
-
633
- if (matchesSearch && matchesCategory) {
634
- row.style.display = '';
635
- visibleRows++;
636
- } else {
637
- row.style.display = 'none';
638
- }
639
- });
640
-
641
- if (visibleCount) {
642
- visibleCount.textContent = visibleRows;
643
- }
644
- }
645
-
646
- if (searchInput) {
647
- searchInput.addEventListener('input', filterTable);
648
- }
649
-
650
- if (categoryFilter) {
651
- categoryFilter.addEventListener('change', filterTable);
652
- }
653
-
654
- // Table sorting
655
- const sortableHeaders = table?.querySelectorAll('th.sortable') || [];
656
- let currentSort = { column: null, direction: 'asc' };
657
-
658
- sortableHeaders.forEach(header => {
659
- header.addEventListener('click', function() {
660
- const column = this.dataset.sort;
661
- const direction = currentSort.column === column && currentSort.direction === 'asc' ? 'desc' : 'asc';
662
-
663
- sortTable(column, direction);
664
- currentSort = { column, direction };
665
-
666
- // Update sort icons
667
- sortableHeaders.forEach(h => {
668
- const icon = h.querySelector('.sort-icon');
669
- if (h === this) {
670
- icon.textContent = direction === 'asc' ? '↑' : '↓';
671
- } else {
672
- icon.textContent = '↕️';
673
- }
674
- });
675
- });
676
- });
677
- });
678
-
679
- function sortTable(column, direction) {
680
- const table = document.getElementById('items-table');
681
- const tbody = table?.querySelector('tbody');
682
- if (!tbody) return;
683
-
684
- const rows = Array.from(tbody.querySelectorAll('.item-row'));
685
-
686
- rows.sort((a, b) => {
687
- let aVal, bVal;
688
-
689
- switch(column) {
690
- case 'name':
691
- aVal = a.dataset.name;
692
- bVal = b.dataset.name;
693
- break;
694
- case 'category':
695
- aVal = a.dataset.category;
696
- bVal = b.dataset.category;
697
- break;
698
- case 'value':
699
- aVal = parseFloat(a.querySelector('.value-badge')?.textContent || '0');
700
- bVal = parseFloat(b.querySelector('.value-badge')?.textContent || '0');
701
- break;
702
- default:
703
- return 0;
704
- }
705
-
706
- if (typeof aVal === 'string') {
707
- aVal = aVal.toLowerCase();
708
- bVal = bVal.toLowerCase();
709
- }
710
-
711
- if (direction === 'asc') {
712
- return aVal > bVal ? 1 : -1;
713
- } else {
714
- return aVal < bVal ? 1 : -1;
715
- }
716
- });
717
-
718
- rows.forEach(row => tbody.appendChild(row));
719
- }
720
-
721
- function resetFilters() {
722
- const searchInput = document.getElementById('item-search');
723
- const categoryFilter = document.getElementById('category-filter');
724
-
725
- if (searchInput) searchInput.value = '';
726
- if (categoryFilter) categoryFilter.value = '';
727
-
728
- // Re-filter to show all rows
729
- const rows = document.querySelectorAll('.item-row');
730
- rows.forEach(row => row.style.display = '');
731
-
732
- const visibleCount = document.getElementById('visible-count');
733
- const totalCount = document.getElementById('total-count');
734
- if (visibleCount && totalCount) {
735
- visibleCount.textContent = totalCount.textContent;
736
- }
737
- }
738
-
739
- function refreshData(featureName) {
740
- // Show loading state
741
- const refreshBtn = document.querySelector('.refresh-btn');
742
- const originalText = refreshBtn.innerHTML;
743
- refreshBtn.innerHTML = '<span class="btn-icon">⏳</span>Refreshing...';
744
- refreshBtn.disabled = true;
745
-
746
- // Reload the page to get fresh data
747
- setTimeout(() => {
748
- window.location.reload();
749
- }, 500);
750
- }
751
-
752
- function showItemDetails(itemName) {
753
- alert('Item details for: ' + itemName + '\n\nThis would typically open a modal or navigate to a detail page.');
754
- }
755
- </script>