Skip to content

Commit 04cc1d6

Browse files
committed
feat: create admin dashboard and management views
- Admin dashboard with real-time metrics and statistics - User management views with CRUD operations - Import management views with progress tracking - Responsive layout with Bootstrap navigation - Shared components for flash messages and navbar
1 parent f0278d2 commit 04cc1d6

File tree

14 files changed

+2438
-0
lines changed

14 files changed

+2438
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<% content_for :title, "Admin Dashboard" %>
2+
3+
<div class="dashboard-container">
4+
<div class="dashboard-header">
5+
<div class="container">
6+
<div class="d-flex justify-content-between align-items-center">
7+
<div>
8+
<h1 class="dashboard-title">Admin Dashboard</h1>
9+
<p class="dashboard-subtitle">
10+
Welcome back, <%= current_user.display_name %>!
11+
<span class="live-indicator">Live</span>
12+
</p>
13+
</div>
14+
<div>
15+
<%= link_to admin_users_path, class: "btn btn-primary" do %>
16+
<i class="bi bi-people me-2"></i>Manage Users
17+
<% end %>
18+
<%= link_to admin_imports_path, class: "btn btn-outline-primary ms-2" do %>
19+
<i class="bi bi-upload me-2"></i>Import Users
20+
<% end %>
21+
</div>
22+
</div>
23+
</div>
24+
</div>
25+
26+
<div class="container">
27+
<!-- Statistics Cards -->
28+
<div class="dashboard-stats">
29+
<div class="row">
30+
<div class="col-md-4">
31+
<div class="card stats-card">
32+
<div class="card-body">
33+
<div class="stats-icon stats-primary">
34+
<i class="bi bi-people"></i>
35+
</div>
36+
<div class="stats-content">
37+
<h2 class="stats-number" id="total-users"><%= @user_stats[:total] %></h2>
38+
<p class="stats-label">Total Users</p>
39+
</div>
40+
</div>
41+
</div>
42+
</div>
43+
44+
<div class="col-md-4">
45+
<div class="card stats-card">
46+
<div class="card-body">
47+
<div class="stats-icon stats-success">
48+
<i class="bi bi-shield-check"></i>
49+
</div>
50+
<div class="stats-content">
51+
<h2 class="stats-number" id="admin-users"><%= @user_stats[:admins] %></h2>
52+
<p class="stats-label">Admin Users</p>
53+
</div>
54+
</div>
55+
</div>
56+
</div>
57+
58+
<div class="col-md-4">
59+
<div class="card stats-card">
60+
<div class="card-body">
61+
<div class="stats-icon stats-warning">
62+
<i class="bi bi-person"></i>
63+
</div>
64+
<div class="stats-content">
65+
<h2 class="stats-number" id="regular-users"><%= @user_stats[:regular_users] %></h2>
66+
<p class="stats-label">Regular Users</p>
67+
</div>
68+
</div>
69+
</div>
70+
</div>
71+
</div>
72+
</div>
73+
74+
<!-- Dashboard Content -->
75+
<div class="dashboard-content">
76+
<div class="row">
77+
<!-- Recent Users -->
78+
<div class="col-lg-8">
79+
<div class="card">
80+
<div class="card-header">
81+
<h5 class="card-title">
82+
<i class="bi bi-clock-history me-2"></i>Recent Users
83+
</h5>
84+
</div>
85+
<div class="card-body">
86+
<% if @recent_users.any? %>
87+
<div class="table-responsive">
88+
<table class="table table-hover user-table">
89+
<thead>
90+
<tr>
91+
<th>Avatar</th>
92+
<th>User</th>
93+
<th>Role</th>
94+
<th>Joined</th>
95+
<th>Actions</th>
96+
</tr>
97+
</thead>
98+
<tbody>
99+
<% @recent_users.each do |user| %>
100+
<tr>
101+
<td class="user-avatar-cell">
102+
<% if user.avatar %>
103+
<%= image_tag user.avatar, class: "user-avatar", alt: user.display_name %>
104+
<% else %>
105+
<div class="user-avatar bg-secondary d-flex align-items-center justify-content-center">
106+
<i class="bi bi-person text-white"></i>
107+
</div>
108+
<% end %>
109+
</td>
110+
<td class="user-info-cell">
111+
<div class="user-name"><%= user.display_name %></div>
112+
<div class="user-email"><%= user.email %></div>
113+
</td>
114+
<td class="user-role-cell">
115+
<span class="role-badge role-<%= user.role %>">
116+
<%= user.role.humanize %>
117+
</span>
118+
</td>
119+
<td>
120+
<small class="text-muted">
121+
<%= time_ago_in_words(user.created_at) %> ago
122+
</small>
123+
</td>
124+
<td class="actions-cell">
125+
<%= link_to admin_user_path(user), class: "btn btn-sm btn-outline-primary" do %>
126+
<i class="bi bi-eye"></i>
127+
<% end %>
128+
<%= link_to edit_admin_user_path(user), class: "btn btn-sm btn-outline-secondary" do %>
129+
<i class="bi bi-pencil"></i>
130+
<% end %>
131+
</td>
132+
</tr>
133+
<% end %>
134+
</tbody>
135+
</table>
136+
</div>
137+
<div class="text-center mt-3">
138+
<%= link_to "View All Users", admin_users_path, class: "btn btn-primary" %>
139+
</div>
140+
<% else %>
141+
<div class="text-center py-4">
142+
<i class="bi bi-people display-1 text-muted"></i>
143+
<h4 class="mt-3">No users yet</h4>
144+
<p class="text-muted">Users will appear here once they register.</p>
145+
</div>
146+
<% end %>
147+
</div>
148+
</div>
149+
</div>
150+
151+
<!-- Quick Stats -->
152+
<div class="col-lg-4">
153+
<div class="card">
154+
<div class="card-header">
155+
<h5 class="card-title">
156+
<i class="bi bi-graph-up me-2"></i>Quick Actions
157+
</h5>
158+
</div>
159+
<div class="card-body">
160+
<div class="d-grid gap-2">
161+
<%= link_to admin_users_path, class: "btn btn-outline-primary" do %>
162+
<i class="bi bi-people me-2"></i>View All Users
163+
<% end %>
164+
165+
<%= link_to new_admin_user_path, class: "btn btn-outline-success" do %>
166+
<i class="bi bi-person-plus me-2"></i>Add New User
167+
<% end %>
168+
169+
<%= link_to admin_imports_path, class: "btn btn-outline-info" do %>
170+
<i class="bi bi-upload me-2"></i>Import Users
171+
<% end %>
172+
173+
<%= link_to admin_users_path(role: 'admin'), class: "btn btn-outline-warning" do %>
174+
<i class="bi bi-shield-check me-2"></i>View Admins
175+
<% end %>
176+
</div>
177+
</div>
178+
</div>
179+
180+
<!-- System Info -->
181+
<div class="card mt-4">
182+
<div class="card-header">
183+
<h5 class="card-title">
184+
<i class="bi bi-info-circle me-2"></i>System Information
185+
</h5>
186+
</div>
187+
<div class="card-body">
188+
<div class="row text-center">
189+
<div class="col-6">
190+
<div class="text-primary">
191+
<i class="bi bi-gem display-4"></i>
192+
</div>
193+
<h6 class="mt-2">Rails</h6>
194+
<small class="text-muted"><%= Rails.version %></small>
195+
</div>
196+
<div class="col-6">
197+
<div class="text-danger">
198+
<i class="bi bi-code-square display-4"></i>
199+
</div>
200+
<h6 class="mt-2">Ruby</h6>
201+
<small class="text-muted"><%= RUBY_VERSION %></small>
202+
</div>
203+
</div>
204+
</div>
205+
</div>
206+
</div>
207+
</div>
208+
</div>
209+
</div>
210+
</div>
211+
212+
<script>
213+
document.addEventListener('DOMContentLoaded', function() {
214+
// Subscribe to dashboard updates
215+
if (typeof App !== 'undefined' && App.cable) {
216+
App.dashboard = App.cable.subscriptions.create("DashboardChannel", {
217+
received: function(data) {
218+
if (data.type === 'stats_update') {
219+
updateDashboardStats(data.stats);
220+
}
221+
}
222+
});
223+
}
224+
225+
function updateDashboardStats(stats) {
226+
// Update the statistics with animation
227+
animateNumber('total-users', stats.total_users);
228+
animateNumber('admin-users', stats.admin_users);
229+
animateNumber('regular-users', stats.regular_users);
230+
}
231+
232+
function animateNumber(elementId, newValue) {
233+
const element = document.getElementById(elementId);
234+
if (!element) return;
235+
236+
const currentValue = parseInt(element.textContent);
237+
const difference = newValue - currentValue;
238+
239+
if (difference !== 0) {
240+
element.style.color = difference > 0 ? '#28a745' : '#dc3545';
241+
element.textContent = newValue;
242+
243+
setTimeout(() => {
244+
element.style.color = '';
245+
}, 2000);
246+
}
247+
}
248+
});
249+
</script>

0 commit comments

Comments
 (0)