rails-image-post-solution 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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +8 -0
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +42 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +264 -0
  7. data/Rakefile +8 -0
  8. data/app/controllers/admin/frozen_posts_controller.rb +79 -0
  9. data/app/controllers/admin/image_reports_controller.rb +81 -0
  10. data/app/controllers/admin/users_controller.rb +100 -0
  11. data/app/controllers/rails_image_post_solution/admin/image_reports_controller.rb +92 -0
  12. data/app/controllers/rails_image_post_solution/image_reports_controller.rb +87 -0
  13. data/app/jobs/rails_image_post_solution/image_moderation_job.rb +97 -0
  14. data/app/models/rails_image_post_solution/image_report.rb +57 -0
  15. data/app/services/rails_image_post_solution/openai_vision_service.rb +128 -0
  16. data/app/views/admin/frozen_posts/index.html.erb +128 -0
  17. data/app/views/admin/image_reports/index.html.erb +94 -0
  18. data/app/views/admin/image_reports/show.html.erb +138 -0
  19. data/app/views/admin/users/index.html.erb +93 -0
  20. data/app/views/admin/users/show.html.erb +198 -0
  21. data/app/views/rails_image_post_solution/admin/image_reports/index.html.erb +100 -0
  22. data/app/views/rails_image_post_solution/admin/image_reports/show.html.erb +154 -0
  23. data/app/views/shared/_image_report_button.html.erb +26 -0
  24. data/app/views/shared/_image_report_modal.html.erb +45 -0
  25. data/config/locales/en.yml +260 -0
  26. data/config/locales/ja.yml +259 -0
  27. data/config/routes.rb +16 -0
  28. data/db/migrate/20250101000001_create_rails_image_post_solution_image_reports.rb +20 -0
  29. data/db/migrate/20250101000002_add_ai_moderation_fields_to_image_reports.rb +12 -0
  30. data/lib/generators/rails_image_post_solution/install/README +27 -0
  31. data/lib/generators/rails_image_post_solution/install/install_generator.rb +47 -0
  32. data/lib/generators/rails_image_post_solution/install/templates/add_ai_moderation_fields.rb.erb +12 -0
  33. data/lib/generators/rails_image_post_solution/install/templates/create_image_reports.rb.erb +19 -0
  34. data/lib/generators/rails_image_post_solution/install/templates/rails_image_post_solution.rb +15 -0
  35. data/lib/rails_image_post_solution/engine.rb +22 -0
  36. data/lib/rails_image_post_solution/version.rb +5 -0
  37. data/lib/rails_image_post_solution.rb +28 -0
  38. metadata +150 -0
@@ -0,0 +1,94 @@
1
+ <div class="admin-container">
2
+ <h1 class="admin-header"><%= t('rails_image_post_solution.admin.index.title') %></h1>
3
+
4
+ <!-- Statistics -->
5
+ <div class="admin-stats">
6
+ <div class="stat-card">
7
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.total') %></div>
8
+ <div class="stat-value"><%= @stats[:total] %></div>
9
+ </div>
10
+ <div class="stat-card pending">
11
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.pending') %></div>
12
+ <div class="stat-value"><%= @stats[:pending] %></div>
13
+ </div>
14
+ <div class="stat-card confirmed">
15
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.confirmed') %></div>
16
+ <div class="stat-value"><%= @stats[:confirmed] %></div>
17
+ </div>
18
+ <div class="stat-card dismissed">
19
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.dismissed') %></div>
20
+ <div class="stat-value"><%= @stats[:dismissed] %></div>
21
+ </div>
22
+ <div class="stat-card reviewed">
23
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.reviewed') %></div>
24
+ <div class="stat-value"><%= @stats[:reviewed] %></div>
25
+ </div>
26
+ </div>
27
+
28
+ <!-- Filters -->
29
+ <div class="admin-filters">
30
+ <%= link_to t('rails_image_post_solution.admin.index.filters.all'), admin_image_reports_path, class: "filter-btn #{'active' if @status_filter == 'all'}" %>
31
+ <%= link_to t('rails_image_post_solution.admin.index.filters.pending'), admin_image_reports_path(status: 'pending'), class: "filter-btn #{'active' if @status_filter == 'pending'}" %>
32
+ <%= link_to t('rails_image_post_solution.admin.index.filters.confirmed'), admin_image_reports_path(status: 'confirmed'), class: "filter-btn #{'active' if @status_filter == 'confirmed'}" %>
33
+ <%= link_to t('rails_image_post_solution.admin.index.filters.dismissed'), admin_image_reports_path(status: 'dismissed'), class: "filter-btn #{'active' if @status_filter == 'dismissed'}" %>
34
+ <%= link_to t('rails_image_post_solution.admin.index.filters.reviewed'), admin_image_reports_path(status: 'reviewed'), class: "filter-btn #{'active' if @status_filter == 'reviewed'}" %>
35
+ </div>
36
+
37
+ <!-- Reports List -->
38
+ <div class="admin-reports-list">
39
+ <% if @reports.any? %>
40
+ <table class="admin-table">
41
+ <thead>
42
+ <tr>
43
+ <th><%= t('rails_image_post_solution.admin.index.table.image') %></th>
44
+ <th><%= t('rails_image_post_solution.admin.index.table.reporter') %></th>
45
+ <th><%= t('rails_image_post_solution.admin.index.table.reason') %></th>
46
+ <th><%= t('rails_image_post_solution.admin.index.table.status') %></th>
47
+ <th><%= t('rails_image_post_solution.admin.index.table.reported_at') %></th>
48
+ <th><%= t('rails_image_post_solution.admin.index.table.reviewer') %></th>
49
+ <th><%= t('rails_image_post_solution.admin.index.table.actions') %></th>
50
+ </tr>
51
+ </thead>
52
+ <tbody>
53
+ <% @reports.each do |report| %>
54
+ <tr class="report-row status-<%= report.status %>">
55
+ <td class="image-cell">
56
+ <% if report.active_storage_attachment.present? %>
57
+ <%= image_tag report.active_storage_attachment.blob, class: "admin-thumbnail", style: "max-width: 100px; max-height: 100px;" %>
58
+ <% else %>
59
+ <span class="text-muted"><%= t('rails_image_post_solution.admin.index.deleted') %></span>
60
+ <% end %>
61
+ </td>
62
+ <td><%= link_to report.user.display_name, user_path(report.user), class: "user-link" %></td>
63
+ <td class="reason-cell"><%= truncate(report.reason, length: 50) || t('rails_image_post_solution.admin.index.no_reason') %></td>
64
+ <td>
65
+ <span class="status-badge status-<%= report.status %>">
66
+ <%= t("rails_image_post_solution.image_report.statuses.#{report.status}") %>
67
+ </span>
68
+ </td>
69
+ <td><%= l(report.created_at, format: :long) %></td>
70
+ <td>
71
+ <% if report.reviewed_by %>
72
+ <%= report.reviewed_by.display_name %>
73
+ <% else %>
74
+ <span class="text-muted">-</span>
75
+ <% end %>
76
+ </td>
77
+ <td class="actions-cell">
78
+ <%= link_to t('rails_image_post_solution.admin.index.detail'), admin_image_report_path(report), class: "btn-action btn-detail" %>
79
+ </td>
80
+ </tr>
81
+ <% end %>
82
+ </tbody>
83
+ </table>
84
+ <% else %>
85
+ <div class="empty-state">
86
+ <p><%= t('rails_image_post_solution.admin.index.no_reports') %></p>
87
+ </div>
88
+ <% end %>
89
+ </div>
90
+
91
+ <div class="admin-actions">
92
+ <%= link_to t('rails_image_post_solution.admin.index.back'), root_path, class: "btn btn-secondary" %>
93
+ </div>
94
+ </div>
@@ -0,0 +1,138 @@
1
+ <div class="admin-container">
2
+ <h1 class="admin-header"><%= t('rails_image_post_solution.admin.show.title') %></h1>
3
+
4
+ <div class="report-detail">
5
+ <!-- Image Display -->
6
+ <div class="detail-section">
7
+ <h3><%= t('rails_image_post_solution.admin.show.reported_image') %></h3>
8
+ <% if @attachment.present? %>
9
+ <div class="reported-image-container">
10
+ <%= image_tag @attachment.blob, class: "reported-image-large", style: "max-width: 800px; max-height: 600px;" %>
11
+ </div>
12
+ <% else %>
13
+ <p class="text-muted"><%= t('rails_image_post_solution.admin.show.image_deleted') %></p>
14
+ <% end %>
15
+ </div>
16
+
17
+ <!-- Report Information -->
18
+ <div class="detail-section">
19
+ <h3><%= t('rails_image_post_solution.admin.show.report_info') %></h3>
20
+ <table class="detail-table">
21
+ <tr>
22
+ <th><%= t('rails_image_post_solution.admin.show.status') %></th>
23
+ <td>
24
+ <span class="status-badge status-<%= @report.status %>">
25
+ <%= t("rails_image_post_solution.image_report.statuses.#{@report.status}") %>
26
+ </span>
27
+ </td>
28
+ </tr>
29
+ <tr>
30
+ <th><%= t('rails_image_post_solution.admin.show.reporter') %></th>
31
+ <td><%= link_to @report.user.display_name, user_path(@report.user), class: "user-link" %></td>
32
+ </tr>
33
+ <tr>
34
+ <th><%= t('rails_image_post_solution.admin.show.reason') %></th>
35
+ <td><%= simple_format(@report.reason) || t('rails_image_post_solution.admin.show.no_reason') %></td>
36
+ </tr>
37
+ <tr>
38
+ <th><%= t('rails_image_post_solution.admin.show.reported_at') %></th>
39
+ <td><%= l(@report.created_at, format: :long) %></td>
40
+ </tr>
41
+ <% if @report.reviewed_by %>
42
+ <tr>
43
+ <th><%= t('rails_image_post_solution.admin.show.reviewer') %></th>
44
+ <td><%= @report.reviewed_by.display_name %></td>
45
+ </tr>
46
+ <tr>
47
+ <th><%= t('rails_image_post_solution.admin.show.reviewed_at') %></th>
48
+ <td><%= l(@report.reviewed_at, format: :long) %></td>
49
+ </tr>
50
+ <% end %>
51
+ </table>
52
+ </div>
53
+
54
+ <!-- Image Poster Information -->
55
+ <% if @reported_user %>
56
+ <div class="detail-section">
57
+ <h3><%= t('rails_image_post_solution.admin.show.image_poster') %></h3>
58
+ <table class="detail-table">
59
+ <tr>
60
+ <th><%= t('rails_image_post_solution.admin.show.username') %></th>
61
+ <td><%= link_to @reported_user.display_name, user_path(@reported_user), class: "user-link" %></td>
62
+ </tr>
63
+ <tr>
64
+ <th><%= t('rails_image_post_solution.admin.show.user_status') %></th>
65
+ <td>
66
+ <% if @reported_user.banned? %>
67
+ <span class="user-status-badge banned"><%= t('rails_image_post_solution.admin.show.banned') %></span>
68
+ <% elsif @reported_user.suspended? %>
69
+ <span class="user-status-badge suspended"><%= t('rails_image_post_solution.admin.show.suspended') %></span>
70
+ <% else %>
71
+ <span class="user-status-badge active"><%= t('rails_image_post_solution.admin.show.active') %></span>
72
+ <% end %>
73
+ </td>
74
+ </tr>
75
+ </table>
76
+ </div>
77
+ <% end %>
78
+
79
+ <!-- Other Reports for Same Image -->
80
+ <div class="detail-section">
81
+ <h3><%= t('rails_image_post_solution.admin.show.all_reports_title', count: @all_reports.count) %></h3>
82
+ <% if @all_reports.count > 1 %>
83
+ <table class="admin-table">
84
+ <thead>
85
+ <tr>
86
+ <th><%= t('rails_image_post_solution.admin.index.table.reporter') %></th>
87
+ <th><%= t('rails_image_post_solution.admin.index.table.reason') %></th>
88
+ <th><%= t('rails_image_post_solution.admin.index.table.status') %></th>
89
+ <th><%= t('rails_image_post_solution.admin.index.table.reported_at') %></th>
90
+ </tr>
91
+ </thead>
92
+ <tbody>
93
+ <% @all_reports.each do |r| %>
94
+ <tr class="<%= 'current-report' if r.id == @report.id %>">
95
+ <td>
96
+ <%= link_to r.user.display_name, user_path(r.user), class: "user-link" %>
97
+ <% if r.id == @report.id %>
98
+ <span class="badge-current"><%= t('rails_image_post_solution.admin.show.current') %></span>
99
+ <% end %>
100
+ </td>
101
+ <td><%= truncate(r.reason, length: 50) || t('rails_image_post_solution.admin.index.no_reason') %></td>
102
+ <td>
103
+ <span class="status-badge status-<%= r.status %>">
104
+ <%= t("rails_image_post_solution.image_report.statuses.#{r.status}") %>
105
+ </span>
106
+ </td>
107
+ <td><%= l(r.created_at, format: :long) %></td>
108
+ </tr>
109
+ <% end %>
110
+ </tbody>
111
+ </table>
112
+ <% else %>
113
+ <p class="text-muted"><%= t('rails_image_post_solution.admin.show.single_report') %></p>
114
+ <% end %>
115
+ </div>
116
+
117
+ <!-- Actions -->
118
+ <div class="detail-actions">
119
+ <% if @report.status == ImageReport::STATUSES[:pending] %>
120
+ <%= button_to t('rails_image_post_solution.admin.show.confirm_button'), confirm_admin_image_report_path(@report), method: :patch, class: "btn btn-danger", data: { turbo_confirm: t('rails_image_post_solution.admin.show.confirm_action') } %>
121
+ <%= button_to t('rails_image_post_solution.admin.show.dismiss_button'), dismiss_admin_image_report_path(@report), method: :patch, class: "btn btn-success", data: { turbo_confirm: t('rails_image_post_solution.admin.show.dismiss_action') } %>
122
+ <% else %>
123
+ <p class="text-muted"><%= t('rails_image_post_solution.admin.show.already_reviewed') %></p>
124
+ <% end %>
125
+
126
+ <% if @reported_user && @reported_user.active? %>
127
+ <div class="user-actions">
128
+ <h4><%= t('rails_image_post_solution.admin.show.user_actions') %></h4>
129
+ <%= link_to t('rails_image_post_solution.admin.show.view_user_admin'), admin_user_path(@reported_user), class: "btn btn-secondary" %>
130
+ </div>
131
+ <% end %>
132
+ </div>
133
+ </div>
134
+
135
+ <div class="admin-actions">
136
+ <%= link_to t('rails_image_post_solution.admin.show.back_to_list'), admin_image_reports_path, class: "btn btn-default" %>
137
+ </div>
138
+ </div>
@@ -0,0 +1,93 @@
1
+ <div class="admin-container">
2
+ <h1 class="admin-header"><%= t('admin.users.index.title') %></h1>
3
+
4
+ <!-- Statistics -->
5
+ <div class="admin-stats">
6
+ <div class="stat-card">
7
+ <div class="stat-label"><%= t('admin.users.index.stats.total') %></div>
8
+ <div class="stat-value"><%= @stats[:total] %></div>
9
+ </div>
10
+ <div class="stat-card active">
11
+ <div class="stat-label"><%= t('admin.users.index.stats.active') %></div>
12
+ <div class="stat-value"><%= @stats[:active] %></div>
13
+ </div>
14
+ <div class="stat-card suspended">
15
+ <div class="stat-label"><%= t('admin.users.index.stats.suspended') %></div>
16
+ <div class="stat-value"><%= @stats[:suspended] %></div>
17
+ </div>
18
+ <div class="stat-card banned">
19
+ <div class="stat-label"><%= t('admin.users.index.stats.banned') %></div>
20
+ <div class="stat-value"><%= @stats[:banned] %></div>
21
+ </div>
22
+ <div class="stat-card admin">
23
+ <div class="stat-label"><%= t('admin.users.index.stats.admin') %></div>
24
+ <div class="stat-value"><%= @stats[:admin] %></div>
25
+ </div>
26
+ </div>
27
+
28
+ <!-- Filters -->
29
+ <div class="admin-filters">
30
+ <%= link_to t('admin.users.index.filters.all'), admin_users_path, class: "filter-btn #{'active' if @status_filter == 'all'}" %>
31
+ <%= link_to t('admin.users.index.filters.active'), admin_users_path(status: 'active'), class: "filter-btn #{'active' if @status_filter == 'active'}" %>
32
+ <%= link_to t('admin.users.index.filters.suspended'), admin_users_path(status: 'suspended'), class: "filter-btn #{'active' if @status_filter == 'suspended'}" %>
33
+ <%= link_to t('admin.users.index.filters.banned'), admin_users_path(status: 'banned'), class: "filter-btn #{'active' if @status_filter == 'banned'}" %>
34
+ <%= link_to t('admin.users.index.filters.admin'), admin_users_path(status: 'admin'), class: "filter-btn #{'active' if @status_filter == 'admin'}" %>
35
+ </div>
36
+
37
+ <!-- User List -->
38
+ <div class="admin-users-list">
39
+ <% if @users.any? %>
40
+ <table class="admin-table">
41
+ <thead>
42
+ <tr>
43
+ <th><%= t('admin.users.index.table.username') %></th>
44
+ <th><%= t('admin.users.index.table.email') %></th>
45
+ <th><%= t('admin.users.index.table.status') %></th>
46
+ <th><%= t('admin.users.index.table.role') %></th>
47
+ <th><%= t('admin.users.index.table.registered_at') %></th>
48
+ <th><%= t('admin.users.index.table.post_count') %></th>
49
+ <th><%= t('admin.users.index.table.actions') %></th>
50
+ </tr>
51
+ </thead>
52
+ <tbody>
53
+ <% @users.each do |user| %>
54
+ <tr class="user-row">
55
+ <td><%= link_to user.display_name, user_path(user), class: "user-link" %></td>
56
+ <td><%= user.email.present? ? user.email : "-" %></td>
57
+ <td>
58
+ <% if user.banned? %>
59
+ <span class="user-status-badge banned"><%= t('admin.users.index.status_badges.banned') %></span>
60
+ <% elsif user.suspended? %>
61
+ <span class="user-status-badge suspended"><%= t('admin.users.index.status_badges.suspended') %></span>
62
+ <% else %>
63
+ <span class="user-status-badge active"><%= t('admin.users.index.status_badges.active') %></span>
64
+ <% end %>
65
+ </td>
66
+ <td>
67
+ <% if user.admin? %>
68
+ <span class="role-badge admin"><%= t('admin.users.index.role_badges.admin') %></span>
69
+ <% else %>
70
+ <span class="role-badge user"><%= t('admin.users.index.role_badges.user') %></span>
71
+ <% end %>
72
+ </td>
73
+ <td><%= l(user.created_at, format: :short) %></td>
74
+ <td><%= user.stages.count %></td>
75
+ <td class="actions-cell">
76
+ <%= link_to t('admin.users.index.detail'), admin_user_path(user), class: "btn-action btn-detail" %>
77
+ </td>
78
+ </tr>
79
+ <% end %>
80
+ </tbody>
81
+ </table>
82
+ <% else %>
83
+ <div class="empty-state">
84
+ <p><%= t('admin.users.index.no_users') %></p>
85
+ </div>
86
+ <% end %>
87
+ </div>
88
+
89
+ <div class="admin-actions">
90
+ <%= link_to t('admin.users.index.view_reports'), admin_image_reports_path, class: "btn btn-secondary" %>
91
+ <%= link_to t('admin.users.index.back'), root_path, class: "btn btn-default" %>
92
+ </div>
93
+ </div>
@@ -0,0 +1,198 @@
1
+ <div class="admin-container">
2
+ <h1 class="admin-header"><%= t('admin.users.show.title', name: @user.display_name) %></h1>
3
+
4
+ <!-- User Basic Information -->
5
+ <div class="detail-section">
6
+ <h3><%= t('admin.users.show.basic_info') %></h3>
7
+ <table class="detail-table">
8
+ <tr>
9
+ <th><%= t('admin.users.show.username') %></th>
10
+ <td><%= @user.username %></td>
11
+ </tr>
12
+ <tr>
13
+ <th><%= t('admin.users.show.nickname') %></th>
14
+ <td><%= @user.nickname.present? ? @user.nickname : "-" %></td>
15
+ </tr>
16
+ <tr>
17
+ <th><%= t('admin.users.show.email') %></th>
18
+ <td><%= @user.email.present? ? @user.email : "-" %></td>
19
+ </tr>
20
+ <tr>
21
+ <th><%= t('admin.users.show.status') %></th>
22
+ <td>
23
+ <% if @user.banned? %>
24
+ <span class="user-status-badge banned"><%= t('admin.users.index.status_badges.banned') %></span>
25
+ <div class="status-detail">
26
+ <strong><%= t('admin.users.show.ban_date') %>:</strong> <%= l(@user.banned_at, format: :long) %><br>
27
+ <% if @user.ban_reason.present? %>
28
+ <strong><%= t('admin.users.show.ban_reason') %>:</strong> <%= simple_format(@user.ban_reason) %>
29
+ <% end %>
30
+ </div>
31
+ <% elsif @user.suspended? %>
32
+ <span class="user-status-badge suspended"><%= t('admin.users.index.status_badges.suspended') %></span>
33
+ <div class="status-detail">
34
+ <strong><%= t('admin.users.show.suspension_expiry') %>:</strong> <%= l(@user.suspended_at, format: :long) %><br>
35
+ <% if @user.suspension_reason.present? %>
36
+ <strong><%= t('admin.users.show.suspension_reason') %>:</strong> <%= simple_format(@user.suspension_reason) %>
37
+ <% end %>
38
+ </div>
39
+ <% else %>
40
+ <span class="user-status-badge active"><%= t('admin.users.index.status_badges.active') %></span>
41
+ <% end %>
42
+ </td>
43
+ </tr>
44
+ <tr>
45
+ <th><%= t('admin.users.show.role') %></th>
46
+ <td>
47
+ <% if @user.admin? %>
48
+ <span class="role-badge admin"><%= t('admin.users.index.role_badges.admin') %></span>
49
+ <% else %>
50
+ <span class="role-badge user"><%= t('admin.users.show.basic_info') == t('admin.users.index.role_badges.user') ? t('admin.users.index.role_badges.user') : t('admin.users.index.role_badges.user') %></span>
51
+ <% end %>
52
+ </td>
53
+ </tr>
54
+ <tr>
55
+ <th><%= t('admin.users.show.registered_date') %></th>
56
+ <td><%= l(@user.created_at, format: :long) %></td>
57
+ </tr>
58
+ <tr>
59
+ <th><%= t('admin.users.show.language') %></th>
60
+ <td><%= t("languages.#{@user.locale}") %></td>
61
+ </tr>
62
+ </table>
63
+ </div>
64
+
65
+ <!-- Activity Statistics -->
66
+ <div class="detail-section">
67
+ <h3><%= t('admin.users.show.activity_stats') %></h3>
68
+ <table class="detail-table">
69
+ <tr>
70
+ <th><%= t('admin.users.show.stage_count') %></th>
71
+ <td><%= @user.stages.count %><%= t('admin.users.show.count_suffix') %></td>
72
+ </tr>
73
+ <tr>
74
+ <th><%= t('admin.users.show.comment_count') %></th>
75
+ <td><%= @user.multiplay_recruitment_comments.count %><%= t('admin.users.show.count_suffix') %></td>
76
+ </tr>
77
+ <tr>
78
+ <th><%= t('admin.users.show.reports_made_count') %></th>
79
+ <td><%= @reports_made.count %><%= t('admin.users.show.count_suffix') %></td>
80
+ </tr>
81
+ <tr>
82
+ <th><%= t('admin.users.show.reports_received_count') %></th>
83
+ <td><%= @reports_received.count %><%= t('admin.users.show.count_suffix') %></td>
84
+ </tr>
85
+ </table>
86
+ </div>
87
+
88
+ <!-- Recent Stage Posts -->
89
+ <div class="detail-section">
90
+ <h3><%= t('admin.users.show.recent_stages') %></h3>
91
+ <% if @stages.any? %>
92
+ <ul class="activity-list">
93
+ <% @stages.each do |stage| %>
94
+ <li>
95
+ <%= link_to stage.title, stage_path(stage), class: "activity-link" %>
96
+ <span class="activity-date"><%= l(stage.created_at, format: :short) %></span>
97
+ </li>
98
+ <% end %>
99
+ </ul>
100
+ <% else %>
101
+ <p class="text-muted"><%= t('admin.users.show.no_posts') %></p>
102
+ <% end %>
103
+ </div>
104
+
105
+ <!-- Recent Comments -->
106
+ <div class="detail-section">
107
+ <h3><%= t('admin.users.show.recent_comments') %></h3>
108
+ <% if @comments.any? %>
109
+ <ul class="activity-list">
110
+ <% @comments.each do |comment| %>
111
+ <li>
112
+ <%= link_to truncate(comment.content, length: 50), multiplay_recruitment_path(comment.multiplay_recruitment), class: "activity-link" %>
113
+ <span class="activity-date"><%= l(comment.created_at, format: :short) %></span>
114
+ </li>
115
+ <% end %>
116
+ </ul>
117
+ <% else %>
118
+ <p class="text-muted"><%= t('admin.users.show.no_comments') %></p>
119
+ <% end %>
120
+ </div>
121
+
122
+ <!-- Report History -->
123
+ <div class="detail-section">
124
+ <h3><%= t('admin.users.show.report_history') %></h3>
125
+ <% if @reports_received.any? %>
126
+ <table class="admin-table">
127
+ <thead>
128
+ <tr>
129
+ <th><%= t('admin.users.show.reporter') %></th>
130
+ <th><%= t('admin.users.show.reason') %></th>
131
+ <th><%= t('admin.users.show.report_status') %></th>
132
+ <th><%= t('admin.users.show.date') %></th>
133
+ </tr>
134
+ </thead>
135
+ <tbody>
136
+ <% @reports_received.each do |report| %>
137
+ <tr>
138
+ <td><%= link_to report.user.display_name, user_path(report.user), class: "user-link" %></td>
139
+ <td><%= truncate(report.reason, length: 50) || t('rails_image_post_solution.admin.index.no_reason') %></td>
140
+ <td>
141
+ <span class="status-badge status-<%= report.status %>">
142
+ <%= t("rails_image_post_solution.image_report.statuses.#{report.status}") %>
143
+ </span>
144
+ </td>
145
+ <td><%= l(report.created_at, format: :short) %></td>
146
+ </tr>
147
+ <% end %>
148
+ </tbody>
149
+ </table>
150
+ <% else %>
151
+ <p class="text-muted"><%= t('admin.users.show.no_reports') %></p>
152
+ <% end %>
153
+ </div>
154
+
155
+ <!-- Admin Actions -->
156
+ <div class="detail-actions">
157
+ <h3><%= t('admin.users.show.admin_actions') %></h3>
158
+
159
+ <% if @user.active? %>
160
+ <div class="action-group">
161
+ <%= form_with url: suspend_admin_user_path(@user), method: :patch, class: "inline-form" do |f| %>
162
+ <div class="form-group-inline">
163
+ <%= f.label :duration, "#{t('admin.users.show.suspend_duration')}:" %>
164
+ <%= f.number_field :duration, value: 7, min: 1, max: 365, class: "form-control-small" %>
165
+ </div>
166
+ <div class="form-group-inline">
167
+ <%= f.label :reason, "#{t('admin.users.show.suspend_reason')}:" %>
168
+ <%= f.text_area :reason, rows: 2, class: "form-control", placeholder: t('admin.users.show.suspend_reason_placeholder') %>
169
+ </div>
170
+ <%= f.submit t('admin.users.show.suspend_button'), class: "btn btn-warning", data: { turbo_confirm: t('admin.users.show.suspend_confirm') } %>
171
+ <% end %>
172
+ </div>
173
+
174
+ <div class="action-group">
175
+ <%= form_with url: ban_admin_user_path(@user), method: :patch, class: "inline-form" do |f| %>
176
+ <div class="form-group-inline">
177
+ <%= f.label :reason, "#{t('admin.users.show.ban_reason')}:" %>
178
+ <%= f.text_area :reason, rows: 2, class: "form-control", placeholder: t('admin.users.show.ban_reason_placeholder') %>
179
+ </div>
180
+ <%= f.submit t('admin.users.show.ban_button'), class: "btn btn-danger", data: { turbo_confirm: t('admin.users.show.ban_confirm') } %>
181
+ <% end %>
182
+ </div>
183
+ <% elsif @user.suspended? %>
184
+ <div class="action-group">
185
+ <%= button_to t('admin.users.show.unsuspend_button'), unsuspend_admin_user_path(@user), method: :patch, class: "btn btn-success", data: { turbo_confirm: t('admin.users.show.unsuspend_confirm') } %>
186
+ </div>
187
+ <% elsif @user.banned? %>
188
+ <div class="action-group">
189
+ <%= button_to t('admin.users.show.unban_button'), unban_admin_user_path(@user), method: :patch, class: "btn btn-success", data: { turbo_confirm: t('admin.users.show.unban_confirm') } %>
190
+ </div>
191
+ <% end %>
192
+ </div>
193
+
194
+ <div class="admin-actions">
195
+ <%= link_to t('admin.users.show.back_to_list'), admin_users_path, class: "btn btn-default" %>
196
+ <%= link_to t('admin.users.show.view_public_profile'), user_path(@user), class: "btn btn-secondary" %>
197
+ </div>
198
+ </div>
@@ -0,0 +1,100 @@
1
+ <div class="admin-container">
2
+ <h1 class="admin-header"><%= t('rails_image_post_solution.admin.index.title') %></h1>
3
+
4
+ <!-- Statistics -->
5
+ <div class="admin-stats">
6
+ <div class="stat-card">
7
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.total') %></div>
8
+ <div class="stat-value"><%= @stats[:total] %></div>
9
+ </div>
10
+ <div class="stat-card pending">
11
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.pending') %></div>
12
+ <div class="stat-value"><%= @stats[:pending] %></div>
13
+ </div>
14
+ <div class="stat-card confirmed">
15
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.confirmed') %></div>
16
+ <div class="stat-value"><%= @stats[:confirmed] %></div>
17
+ </div>
18
+ <div class="stat-card dismissed">
19
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.dismissed') %></div>
20
+ <div class="stat-value"><%= @stats[:dismissed] %></div>
21
+ </div>
22
+ <div class="stat-card reviewed">
23
+ <div class="stat-label"><%= t('rails_image_post_solution.admin.index.stats.reviewed') %></div>
24
+ <div class="stat-value"><%= @stats[:reviewed] %></div>
25
+ </div>
26
+ </div>
27
+
28
+ <!-- Filters -->
29
+ <div class="admin-filters">
30
+ <%= link_to t('rails_image_post_solution.admin.index.filters.all'), rails_image_post_solution.admin_image_reports_path, class: "filter-btn #{'active' if @status_filter == 'all'}" %>
31
+ <%= link_to t('rails_image_post_solution.admin.index.filters.pending'), rails_image_post_solution.admin_image_reports_path(status: 'pending'), class: "filter-btn #{'active' if @status_filter == 'pending'}" %>
32
+ <%= link_to t('rails_image_post_solution.admin.index.filters.confirmed'), rails_image_post_solution.admin_image_reports_path(status: 'confirmed'), class: "filter-btn #{'active' if @status_filter == 'confirmed'}" %>
33
+ <%= link_to t('rails_image_post_solution.admin.index.filters.dismissed'), rails_image_post_solution.admin_image_reports_path(status: 'dismissed'), class: "filter-btn #{'active' if @status_filter == 'dismissed'}" %>
34
+ <%= link_to t('rails_image_post_solution.admin.index.filters.reviewed'), rails_image_post_solution.admin_image_reports_path(status: 'reviewed'), class: "filter-btn #{'active' if @status_filter == 'reviewed'}" %>
35
+ </div>
36
+
37
+ <!-- Reports List -->
38
+ <div class="admin-reports-list">
39
+ <% if @reports.any? %>
40
+ <table class="admin-table">
41
+ <thead>
42
+ <tr>
43
+ <th><%= t('rails_image_post_solution.admin.index.table.image') %></th>
44
+ <th><%= t('rails_image_post_solution.admin.index.table.reporter') %></th>
45
+ <th><%= t('rails_image_post_solution.admin.index.table.reason') %></th>
46
+ <th><%= t('rails_image_post_solution.admin.index.table.status') %></th>
47
+ <th><%= t('rails_image_post_solution.admin.index.table.reported_at') %></th>
48
+ <th><%= t('rails_image_post_solution.admin.index.table.reviewer') %></th>
49
+ <th><%= t('rails_image_post_solution.admin.index.table.actions') %></th>
50
+ </tr>
51
+ </thead>
52
+ <tbody>
53
+ <% @reports.each do |report| %>
54
+ <tr class="report-row status-<%= report.status %>">
55
+ <td class="image-cell">
56
+ <% if report.active_storage_attachment.present? %>
57
+ <%= image_tag report.active_storage_attachment.blob, class: "admin-thumbnail", style: "max-width: 100px; max-height: 100px;" %>
58
+ <% else %>
59
+ <span class="text-muted"><%= t('rails_image_post_solution.admin.index.deleted') %></span>
60
+ <% end %>
61
+ </td>
62
+ <td>
63
+ <% if report.user %>
64
+ <%= report.user.try(:display_name) || report.user.try(:name) || "Unknown" %>
65
+ <% else %>
66
+ <span class="text-muted"><%= t('rails_image_post_solution.admin.index.system') %></span>
67
+ <% end %>
68
+ </td>
69
+ <td class="reason-cell"><%= truncate(report.reason, length: 50) || t('rails_image_post_solution.admin.index.no_reason') %></td>
70
+ <td>
71
+ <span class="status-badge status-<%= report.status %>">
72
+ <%= t("rails_image_post_solution.image_report.statuses.#{report.status}") %>
73
+ </span>
74
+ </td>
75
+ <td><%= l(report.created_at, format: :long) %></td>
76
+ <td>
77
+ <% if report.reviewed_by %>
78
+ <%= report.reviewed_by.try(:display_name) || report.reviewed_by.try(:name) || "Unknown" %>
79
+ <% else %>
80
+ <span class="text-muted">-</span>
81
+ <% end %>
82
+ </td>
83
+ <td class="actions-cell">
84
+ <%= link_to t('rails_image_post_solution.admin.index.detail'), rails_image_post_solution.admin_image_report_path(report), class: "btn-action btn-detail" %>
85
+ </td>
86
+ </tr>
87
+ <% end %>
88
+ </tbody>
89
+ </table>
90
+ <% else %>
91
+ <div class="empty-state">
92
+ <p><%= t('rails_image_post_solution.admin.index.no_reports') %></p>
93
+ </div>
94
+ <% end %>
95
+ </div>
96
+
97
+ <div class="admin-actions">
98
+ <%= link_to t('rails_image_post_solution.admin.index.back'), main_app.root_path, class: "btn btn-secondary" %>
99
+ </div>
100
+ </div>