ruby_cms 0.2.0.4 → 0.2.0.8
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 +25 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +9 -1
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb +3 -3
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb +8 -8
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb +15 -5
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb +57 -12
- data/app/controllers/concerns/ruby_cms/page_tracking.rb +15 -0
- data/app/controllers/ruby_cms/admin/analytics_controller.rb +12 -0
- data/app/controllers/ruby_cms/admin/commands_controller.rb +6 -6
- data/app/javascript/controllers/ruby_cms/bulk_action_table_controller.js +65 -13
- data/app/services/ruby_cms/analytics/report.rb +119 -7
- data/app/services/ruby_cms/command_runner.rb +3 -3
- data/app/views/layouts/ruby_cms/admin.html.erb +1 -1
- data/app/views/ruby_cms/admin/analytics/index.html.erb +160 -67
- data/app/views/ruby_cms/admin/analytics/partials/_browser_device.html.erb +21 -15
- data/app/views/ruby_cms/admin/analytics/partials/_daily_activity_chart.html.erb +36 -16
- data/app/views/ruby_cms/admin/analytics/partials/_hourly_activity_chart.html.erb +29 -13
- data/app/views/ruby_cms/admin/analytics/partials/_landing_pages.html.erb +27 -15
- data/app/views/ruby_cms/admin/analytics/partials/_os_stats.html.erb +29 -18
- data/app/views/ruby_cms/admin/analytics/partials/_popular_pages.html.erb +17 -12
- data/app/views/ruby_cms/admin/analytics/partials/_recent_activity.html.erb +25 -14
- data/app/views/ruby_cms/admin/analytics/partials/_top_referrers.html.erb +27 -15
- data/app/views/ruby_cms/admin/analytics/partials/_top_visitors.html.erb +20 -10
- data/app/views/ruby_cms/admin/analytics/partials/_utm_sources.html.erb +27 -15
- data/app/views/ruby_cms/admin/content_blocks/index.html.erb +8 -1
- data/config/locales/en.yml +69 -5
- data/config/locales/nl.yml +98 -0
- data/lib/generators/ruby_cms/install_generator.rb +45 -89
- data/lib/generators/ruby_cms/templates/admin.html.erb +1 -1
- data/lib/generators/ruby_cms/templates/ruby_cms.rb +40 -3
- data/lib/ruby_cms/settings_registry.rb +35 -0
- data/lib/ruby_cms/version.rb +1 -1
- metadata +1 -1
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="flex items-center justify-between gap-3 px-4 py-
|
|
3
|
-
<div>
|
|
4
|
-
<
|
|
5
|
-
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center justify-between gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="flex items-center gap-3">
|
|
4
|
+
<div class="rounded-md bg-sky-50 p-1.5 flex-shrink-0">
|
|
5
|
+
<svg class="w-4 h-4 text-sky-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z"/></svg>
|
|
6
|
+
</div>
|
|
7
|
+
<div>
|
|
8
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.daily_activity", default: "Daily Activity") %></p>
|
|
9
|
+
<p class="text-xs text-muted-foreground"><%= t("ruby_cms.admin.analytics.page_views_visitors_over_time", default: "Page views and visitors over time") %></p>
|
|
10
|
+
</div>
|
|
6
11
|
</div>
|
|
7
|
-
<div class="flex items-center gap-4 text-
|
|
12
|
+
<div class="flex items-center gap-4 text-xs text-muted-foreground flex-shrink-0">
|
|
8
13
|
<span class="inline-flex items-center gap-1.5"><span class="h-2 w-2 rounded-full bg-primary"></span><%= t("ruby_cms.admin.analytics.views", default: "Views") %></span>
|
|
9
14
|
<span class="inline-flex items-center gap-1.5"><span class="h-2 w-2 rounded-full bg-emerald-500"></span><%= t("ruby_cms.admin.analytics.visitors", default: "Visitors") %></span>
|
|
10
15
|
</div>
|
|
11
16
|
</div>
|
|
12
17
|
|
|
13
|
-
<div class="px-4 py-
|
|
18
|
+
<div class="px-4 py-4">
|
|
14
19
|
<% if @daily_activity.present? && @daily_activity.any? %>
|
|
15
20
|
<%
|
|
16
21
|
daily_arr = @daily_activity.is_a?(Hash) ? @daily_activity.to_a : @daily_activity
|
|
@@ -18,11 +23,11 @@
|
|
|
18
23
|
max_visitors = (@daily_visitors || {}).values.map(&:to_i).max || 0
|
|
19
24
|
max_value = [max_views, max_visitors].max
|
|
20
25
|
max_value = 1 if max_value.zero?
|
|
21
|
-
chart_h =
|
|
26
|
+
chart_h = 160
|
|
22
27
|
%>
|
|
23
28
|
|
|
24
29
|
<div class="grid grid-cols-[2.75rem_1fr] gap-2">
|
|
25
|
-
<div class="flex flex-col justify-between
|
|
30
|
+
<div class="flex flex-col justify-between text-xs text-muted-foreground tabular-nums" style="height:<%= chart_h %>px">
|
|
26
31
|
<% [100, 75, 50, 25, 0].each do |pct| %>
|
|
27
32
|
<div class="text-right"><%= number_with_delimiter((max_value * pct / 100.0).round) %></div>
|
|
28
33
|
<% end %>
|
|
@@ -30,18 +35,32 @@
|
|
|
30
35
|
|
|
31
36
|
<div class="relative">
|
|
32
37
|
<div class="absolute inset-0 flex flex-col justify-between pointer-events-none">
|
|
33
|
-
<% 5.times do %><div class="h-px bg-border/
|
|
38
|
+
<% 5.times do %><div class="h-px bg-border/30"></div><% end %>
|
|
34
39
|
</div>
|
|
35
40
|
|
|
36
|
-
<div class="
|
|
41
|
+
<div class="flex items-end gap-1" style="height:<%= chart_h %>px">
|
|
37
42
|
<% daily_arr.each do |date, views_count| %>
|
|
38
43
|
<% visitors_count = (@daily_visitors || {})[date] || 0 %>
|
|
39
|
-
<% views_h =
|
|
40
|
-
|
|
44
|
+
<% views_h = if views_count.to_i.positive?
|
|
45
|
+
[((views_count.to_f / max_value) * chart_h).round, 2].max
|
|
46
|
+
else
|
|
47
|
+
0
|
|
48
|
+
end %>
|
|
49
|
+
<% visitors_h = if visitors_count.to_i.positive?
|
|
50
|
+
[((visitors_count.to_f / max_value) * chart_h).round, 2].max
|
|
51
|
+
else
|
|
52
|
+
0
|
|
53
|
+
end %>
|
|
41
54
|
<div class="flex flex-col items-center gap-1.5 flex-1 min-w-0 group">
|
|
42
55
|
<div class="w-full flex items-end justify-center gap-px" title="<%= format_chart_date(date) %> — Views: <%= number_with_delimiter(views_count) %>, Visitors: <%= number_with_delimiter(visitors_count) %>">
|
|
43
|
-
<div
|
|
44
|
-
|
|
56
|
+
<div
|
|
57
|
+
class="flex-1 rounded-t transition-colors group-hover:opacity-90"
|
|
58
|
+
style="height:<%= views_h %>px; max-width:8px; background-color:hsl(var(--primary) / 0.75);"
|
|
59
|
+
></div>
|
|
60
|
+
<div
|
|
61
|
+
class="flex-1 rounded-t transition-colors group-hover:opacity-90"
|
|
62
|
+
style="height:<%= visitors_h %>px; max-width:8px; background-color:rgb(16 185 129 / 0.75);"
|
|
63
|
+
></div>
|
|
45
64
|
</div>
|
|
46
65
|
<div class="text-[10px] text-muted-foreground truncate w-full text-center"><%= format_chart_date_short(date) %></div>
|
|
47
66
|
</div>
|
|
@@ -50,7 +69,8 @@
|
|
|
50
69
|
</div>
|
|
51
70
|
</div>
|
|
52
71
|
<% else %>
|
|
53
|
-
<div class="flex items-center justify-center h-
|
|
72
|
+
<div class="flex flex-col items-center justify-center h-44 gap-2">
|
|
73
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z"/></svg>
|
|
54
74
|
<p class="text-sm text-muted-foreground"><%= t("ruby_cms.admin.analytics.no_daily_data", default: "No daily activity data") %></p>
|
|
55
75
|
</div>
|
|
56
76
|
<% end %>
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="px-4 py-
|
|
3
|
-
<
|
|
4
|
-
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-emerald-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.hourly_activity", default: "Hourly Activity") %></p>
|
|
8
|
+
<p class="text-xs text-muted-foreground"><%= t("ruby_cms.admin.analytics.views_by_hour", default: "Views by hour of day") %></p>
|
|
9
|
+
</div>
|
|
5
10
|
</div>
|
|
6
11
|
|
|
7
|
-
<div class="px-4 py-
|
|
12
|
+
<div class="px-4 py-4">
|
|
8
13
|
<% if @hourly_activity.present? && @hourly_activity.any? %>
|
|
9
14
|
<%
|
|
10
15
|
hourly_arr = @hourly_activity.is_a?(Hash) ? @hourly_activity.to_a : @hourly_activity
|
|
@@ -13,12 +18,13 @@
|
|
|
13
18
|
total = group.sum { |_, c| c.to_i }
|
|
14
19
|
[hours, total]
|
|
15
20
|
end
|
|
16
|
-
max_value = grouped.map { |_, c| c }.max
|
|
17
|
-
|
|
21
|
+
max_value = grouped.map { |_, c| c.to_i }.max.to_f
|
|
22
|
+
max_value = 1.0 unless max_value.positive? && max_value.finite?
|
|
23
|
+
chart_h = 160
|
|
18
24
|
%>
|
|
19
25
|
|
|
20
26
|
<div class="grid grid-cols-[2.75rem_1fr] gap-2">
|
|
21
|
-
<div class="flex flex-col justify-between
|
|
27
|
+
<div class="flex flex-col justify-between text-xs text-muted-foreground tabular-nums" style="height:<%= chart_h %>px">
|
|
22
28
|
<% [100, 75, 50, 25, 0].each do |pct| %>
|
|
23
29
|
<div class="text-right"><%= number_with_delimiter((max_value * pct / 100.0).round) %></div>
|
|
24
30
|
<% end %>
|
|
@@ -26,15 +32,24 @@
|
|
|
26
32
|
|
|
27
33
|
<div class="relative">
|
|
28
34
|
<div class="absolute inset-0 flex flex-col justify-between pointer-events-none">
|
|
29
|
-
<% 5.times do %><div class="h-px bg-border/
|
|
35
|
+
<% 5.times do %><div class="h-px bg-border/30"></div><% end %>
|
|
30
36
|
</div>
|
|
31
37
|
|
|
32
|
-
<div class="
|
|
38
|
+
<div class="flex items-end gap-1.5" style="height:<%= chart_h %>px">
|
|
33
39
|
<% grouped.each do |hours, count| %>
|
|
34
|
-
<%
|
|
40
|
+
<% ratio = count.to_f / max_value %>
|
|
41
|
+
<% ratio = 0.0 unless ratio.finite? %>
|
|
42
|
+
<% h = if count.to_i.positive?
|
|
43
|
+
[[(ratio * chart_h).round, 2].max, chart_h].min
|
|
44
|
+
else
|
|
45
|
+
0
|
|
46
|
+
end %>
|
|
35
47
|
<div class="flex flex-col items-center gap-1.5 flex-1 min-w-0 group">
|
|
36
48
|
<div class="w-full flex items-end justify-center" title="<%= hours.size > 1 ? "#{hours.first}:00–#{hours.last}:00" : "#{hours.first}:00" %> — <%= number_with_delimiter(count) %> views">
|
|
37
|
-
<div
|
|
49
|
+
<div
|
|
50
|
+
class="w-full rounded-t transition-colors group-hover:opacity-90"
|
|
51
|
+
style="height:<%= h %>px; max-width:12px; background-color:rgb(16 185 129 / 0.75);"
|
|
52
|
+
></div>
|
|
38
53
|
</div>
|
|
39
54
|
<div class="text-[10px] text-muted-foreground truncate w-full text-center"><%= hours.size > 1 ? "#{hours.first}–#{hours.last}h" : "#{hours.first}h" %></div>
|
|
40
55
|
</div>
|
|
@@ -43,7 +58,8 @@
|
|
|
43
58
|
</div>
|
|
44
59
|
</div>
|
|
45
60
|
<% else %>
|
|
46
|
-
<div class="flex items-center justify-center h-
|
|
61
|
+
<div class="flex flex-col items-center justify-center h-44 gap-2">
|
|
62
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
47
63
|
<p class="text-sm text-muted-foreground"><%= t("ruby_cms.admin.analytics.no_hourly_data", default: "No hourly activity data") %></p>
|
|
48
64
|
</div>
|
|
49
65
|
<% end %>
|
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="px-
|
|
3
|
-
<
|
|
4
|
-
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-teal-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.landing_pages", default: "Landing Pages") %></p>
|
|
8
|
+
<p class="text-xs text-muted-foreground">First pages visitors see</p>
|
|
9
|
+
</div>
|
|
5
10
|
</div>
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
<%
|
|
12
|
+
<% if @landing_pages.present? && @landing_pages.any? %>
|
|
13
|
+
<% total = @landing_pages.values.sum.to_f %>
|
|
14
|
+
<div class="divide-y divide-border/30">
|
|
9
15
|
<% @landing_pages.first(6).each do |page, count| %>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
16
|
+
<% pct = total.positive? ? (count / total * 100).round(1) : 0 %>
|
|
17
|
+
<div class="relative flex items-center justify-between gap-3 px-4 py-2.5">
|
|
18
|
+
<div class="absolute inset-y-0 left-0 bg-teal-500/5" style="width: <%= pct %>%"></div>
|
|
19
|
+
<span class="relative z-10 text-sm text-foreground truncate" title="<%= page %>"><%= truncate(page.to_s.gsub(%r{https?://[^/]+}, ''), length: 38) %></span>
|
|
20
|
+
<div class="relative z-10 flex items-center gap-2 flex-shrink-0">
|
|
21
|
+
<span class="text-xs text-muted-foreground tabular-nums"><%= pct %>%</span>
|
|
22
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
23
|
+
</div>
|
|
13
24
|
</div>
|
|
14
25
|
<% end %>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
</div>
|
|
27
|
+
<% else %>
|
|
28
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
29
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/></svg>
|
|
30
|
+
<p class="text-sm text-muted-foreground">No landing page data</p>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
21
33
|
</div>
|
|
@@ -1,26 +1,37 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="px-
|
|
3
|
-
<
|
|
4
|
-
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-indigo-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v4M9 3v18m0 0h10a2 2 0 002-2V9M9 21H5a2 2 0 01-2-2V9m0 0h18"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.operating_systems", default: "Operating Systems") %></p>
|
|
8
|
+
<p class="text-xs text-muted-foreground">Visitor OS breakdown</p>
|
|
9
|
+
</div>
|
|
5
10
|
</div>
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
<%
|
|
9
|
-
|
|
12
|
+
<% if @os_stats.present? && @os_stats.any? %>
|
|
13
|
+
<% total = @os_stats.values.sum.to_f %>
|
|
14
|
+
<div class="px-4 py-3 space-y-2.5">
|
|
10
15
|
<% @os_stats.sort_by { |_, v| -v }.first(6).each do |os, count| %>
|
|
11
16
|
<% pct = total.positive? ? (count / total * 100).round(1) : 0 %>
|
|
12
|
-
<div
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
+
<div>
|
|
18
|
+
<div class="flex items-center justify-between gap-3 mb-1">
|
|
19
|
+
<span class="text-sm text-foreground"><%= os %></span>
|
|
20
|
+
<div class="flex items-center gap-2 flex-shrink-0">
|
|
21
|
+
<span class="text-xs text-muted-foreground tabular-nums"><%= pct %>%</span>
|
|
22
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="w-full h-1.5 bg-muted rounded-full overflow-hidden">
|
|
26
|
+
<div class="h-full bg-indigo-400 rounded-full" style="width: <%= pct %>%"></div>
|
|
17
27
|
</div>
|
|
18
28
|
</div>
|
|
19
29
|
<% end %>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
</div>
|
|
31
|
+
<% else %>
|
|
32
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
33
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v4M9 3v18m0 0h10a2 2 0 002-2V9M9 21H5a2 2 0 01-2-2V9m0 0h18"/></svg>
|
|
34
|
+
<p class="text-sm text-muted-foreground"><%= t("ruby_cms.admin.analytics.no_data", default: "No data yet.") %></p>
|
|
35
|
+
</div>
|
|
36
|
+
<% end %>
|
|
26
37
|
</div>
|
|
@@ -1,34 +1,39 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="flex items-center
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-blue-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
|
5
|
+
</div>
|
|
3
6
|
<div>
|
|
4
7
|
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.popular_pages", default: "Popular Pages") %></p>
|
|
5
|
-
<p class="text-
|
|
8
|
+
<p class="text-xs text-muted-foreground">Most viewed pages in selected range</p>
|
|
6
9
|
</div>
|
|
7
10
|
</div>
|
|
8
11
|
|
|
9
12
|
<% if @popular_pages.present? %>
|
|
10
13
|
<% total = @popular_pages.values.sum.to_f %>
|
|
11
|
-
<div class="divide-y divide-border/
|
|
14
|
+
<div class="divide-y divide-border/30">
|
|
12
15
|
<% @popular_pages.first(8).each_with_index do |(page_name, count), idx| %>
|
|
13
16
|
<% pct = total.positive? ? (count / total * 100).round(1) : 0 %>
|
|
14
17
|
<%= link_to page_details_ruby_cms_admin_analytics_path(page_name:, start_date: @start_date, end_date: @end_date, period: @period),
|
|
15
|
-
class: "group relative flex items-center justify-between gap-
|
|
16
|
-
<div class="absolute inset-y-0 left-0 bg-
|
|
17
|
-
<div class="relative z-10 flex items-center gap-
|
|
18
|
+
class: "group relative flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-muted/40 transition-colors" do %>
|
|
19
|
+
<div class="absolute inset-y-0 left-0 bg-blue-500/5 transition-all" style="width: <%= pct %>%"></div>
|
|
20
|
+
<div class="relative z-10 flex items-center gap-3 min-w-0">
|
|
18
21
|
<span class="text-xs font-medium text-muted-foreground tabular-nums w-5 text-right flex-shrink-0"><%= idx + 1 %></span>
|
|
19
|
-
<p class="text-
|
|
22
|
+
<p class="text-sm text-foreground truncate group-hover:text-primary transition-colors"><%= page_name.to_s.humanize %></p>
|
|
20
23
|
</div>
|
|
21
|
-
<div class="relative z-10 flex items-center gap-2 flex-shrink-0">
|
|
24
|
+
<div class="relative z-10 flex items-center gap-2.5 flex-shrink-0">
|
|
22
25
|
<span class="text-xs text-muted-foreground tabular-nums"><%= pct %>%</span>
|
|
23
|
-
<span class="text-
|
|
26
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
27
|
+
<svg class="w-3.5 h-3.5 text-muted-foreground/40 group-hover:text-primary/60 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
24
28
|
</div>
|
|
25
29
|
<% end %>
|
|
26
30
|
<% end %>
|
|
27
31
|
</div>
|
|
28
32
|
<% else %>
|
|
29
|
-
<div class="px-4 py-
|
|
33
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
34
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
|
30
35
|
<p class="text-sm font-medium text-foreground"><%= t("ruby_cms.admin.analytics.no_data", default: "No data yet.") %></p>
|
|
31
|
-
<p class="
|
|
36
|
+
<p class="text-xs text-muted-foreground">Track some traffic to see page analytics</p>
|
|
32
37
|
</div>
|
|
33
38
|
<% end %>
|
|
34
39
|
</div>
|
|
@@ -1,33 +1,44 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="px-4 py-3 border-b border-border/40">
|
|
3
|
-
<
|
|
4
|
-
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-green-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.recent_activity", default: "Recent Activity") %></p>
|
|
8
|
+
<p class="text-xs text-muted-foreground">Latest page views</p>
|
|
9
|
+
</div>
|
|
5
10
|
</div>
|
|
6
11
|
|
|
7
12
|
<% if @recent_page_views.present? && @recent_page_views.any? %>
|
|
8
|
-
<div class="divide-y divide-border/
|
|
13
|
+
<div class="divide-y divide-border/30">
|
|
9
14
|
<% @recent_page_views.first(6).each do |event| %>
|
|
10
15
|
<% page_name = event.respond_to?(:page_name) ? event.page_name : nil %>
|
|
11
16
|
<% if page_name.present? %>
|
|
12
17
|
<%= link_to page_details_ruby_cms_admin_analytics_path(page_name: page_name, start_date: @start_date, end_date: @end_date, period: @period),
|
|
13
|
-
class: "group flex items-center
|
|
14
|
-
<div class="
|
|
15
|
-
|
|
16
|
-
<p class="text-
|
|
18
|
+
class: "group flex items-center gap-3 px-4 py-2.5 hover:bg-muted/40 transition-colors" do %>
|
|
19
|
+
<div class="w-1.5 h-1.5 rounded-full bg-green-400 flex-shrink-0 mt-0.5"></div>
|
|
20
|
+
<div class="min-w-0 flex-1">
|
|
21
|
+
<p class="text-sm text-foreground truncate group-hover:text-primary transition-colors"><%= page_name.to_s.humanize %></p>
|
|
22
|
+
<p class="text-xs text-muted-foreground mt-0.5"><%= time_ago_in_words(event.time) %> ago</p>
|
|
17
23
|
</div>
|
|
24
|
+
<svg class="w-3.5 h-3.5 text-muted-foreground/40 group-hover:text-primary/60 transition-colors flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
18
25
|
<% end %>
|
|
19
26
|
<% else %>
|
|
20
|
-
<div class="px-4 py-2">
|
|
21
|
-
<
|
|
22
|
-
<
|
|
27
|
+
<div class="flex items-center gap-3 px-4 py-2.5">
|
|
28
|
+
<div class="w-1.5 h-1.5 rounded-full bg-muted-foreground/30 flex-shrink-0 mt-0.5"></div>
|
|
29
|
+
<div class="min-w-0">
|
|
30
|
+
<p class="text-sm text-muted-foreground"><%= t("ruby_cms.admin.analytics.page", default: "Page") %></p>
|
|
31
|
+
<p class="text-xs text-muted-foreground mt-0.5"><%= time_ago_in_words(event.time) %> ago</p>
|
|
32
|
+
</div>
|
|
23
33
|
</div>
|
|
24
34
|
<% end %>
|
|
25
35
|
<% end %>
|
|
26
36
|
</div>
|
|
27
37
|
<% else %>
|
|
28
|
-
<div class="px-4 py-10
|
|
38
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
39
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
|
29
40
|
<p class="text-sm font-medium text-foreground"><%= t("ruby_cms.admin.analytics.no_activity", default: "No activity recorded") %></p>
|
|
30
|
-
<p class="
|
|
41
|
+
<p class="text-xs text-muted-foreground">No page views in selected range</p>
|
|
31
42
|
</div>
|
|
32
43
|
<% end %>
|
|
33
44
|
</div>
|
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="px-4 py-3 border-b border-border/40">
|
|
3
|
-
<
|
|
4
|
-
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-purple-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.top_referrers", default: "Top Referrers") %></p>
|
|
8
|
+
<p class="text-xs text-muted-foreground">Where visitors came from</p>
|
|
9
|
+
</div>
|
|
5
10
|
</div>
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
<%
|
|
12
|
+
<% if @top_referrers.present? && @top_referrers.any? %>
|
|
13
|
+
<% total = @top_referrers.values.sum.to_f %>
|
|
14
|
+
<div class="divide-y divide-border/30">
|
|
9
15
|
<% @top_referrers.first(6).each do |referrer, count| %>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
16
|
+
<% pct = total.positive? ? (count / total * 100).round(1) : 0 %>
|
|
17
|
+
<div class="relative flex items-center justify-between gap-3 px-4 py-2.5">
|
|
18
|
+
<div class="absolute inset-y-0 left-0 bg-purple-500/5" style="width: <%= pct %>%"></div>
|
|
19
|
+
<span class="relative z-10 text-sm text-foreground truncate" title="<%= referrer %>"><%= truncate(referrer.to_s.gsub(%r{https?://}, ''), length: 38) %></span>
|
|
20
|
+
<div class="relative z-10 flex items-center gap-2 flex-shrink-0">
|
|
21
|
+
<span class="text-xs text-muted-foreground tabular-nums"><%= pct %>%</span>
|
|
22
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
23
|
+
</div>
|
|
13
24
|
</div>
|
|
14
25
|
<% end %>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
</div>
|
|
27
|
+
<% else %>
|
|
28
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
29
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
|
|
30
|
+
<p class="text-sm text-muted-foreground"><%= t("ruby_cms.admin.analytics.no_referrers", default: "No referrers recorded") %></p>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
21
33
|
</div>
|
|
@@ -1,28 +1,38 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="flex items-center
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-violet-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-violet-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
|
5
|
+
</div>
|
|
3
6
|
<div>
|
|
4
7
|
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.top_visitors", default: "Top Visitors") %></p>
|
|
5
|
-
<p class="text-
|
|
8
|
+
<p class="text-xs text-muted-foreground">Most active IPs in selected range</p>
|
|
6
9
|
</div>
|
|
7
10
|
</div>
|
|
8
11
|
|
|
9
12
|
<% if @top_visitors.present? %>
|
|
10
|
-
|
|
13
|
+
<% max_count = @top_visitors.values.first.to_i %>
|
|
14
|
+
<div class="divide-y divide-border/30">
|
|
11
15
|
<% @top_visitors.first(8).each_with_index do |(ip, count), idx| %>
|
|
16
|
+
<% pct = max_count.positive? ? (count.to_f / max_count * 100).round(0) : 0 %>
|
|
12
17
|
<%= link_to visitor_details_ruby_cms_admin_analytics_path(ip_address: ip, start_date: @start_date, end_date: @end_date, period: @period),
|
|
13
|
-
class: "group flex items-center justify-between gap-
|
|
14
|
-
<div class="
|
|
18
|
+
class: "group relative flex items-center justify-between gap-3 px-4 py-2.5 hover:bg-muted/40 transition-colors" do %>
|
|
19
|
+
<div class="absolute inset-y-0 left-0 bg-violet-500/5 transition-all" style="width: <%= pct %>%"></div>
|
|
20
|
+
<div class="relative z-10 flex items-center gap-3 min-w-0">
|
|
15
21
|
<span class="text-xs font-medium text-muted-foreground tabular-nums w-5 text-right flex-shrink-0"><%= idx + 1 %></span>
|
|
16
|
-
<p class="text-
|
|
22
|
+
<p class="text-sm font-mono text-foreground truncate group-hover:text-primary transition-colors"><%= ip %></p>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="relative z-10 flex items-center gap-2 flex-shrink-0">
|
|
25
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
26
|
+
<svg class="w-3.5 h-3.5 text-muted-foreground/40 group-hover:text-primary/60 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
17
27
|
</div>
|
|
18
|
-
<span class="text-xs font-semibold tabular-nums text-foreground flex-shrink-0"><%= number_with_delimiter(count) %></span>
|
|
19
28
|
<% end %>
|
|
20
29
|
<% end %>
|
|
21
30
|
</div>
|
|
22
31
|
<% else %>
|
|
23
|
-
<div class="px-4 py-
|
|
32
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
33
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
|
|
24
34
|
<p class="text-sm font-medium text-foreground"><%= t("ruby_cms.admin.analytics.no_data", default: "No data yet.") %></p>
|
|
25
|
-
<p class="
|
|
35
|
+
<p class="text-xs text-muted-foreground">No visitors in selected range</p>
|
|
26
36
|
</div>
|
|
27
37
|
<% end %>
|
|
28
38
|
</div>
|
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
<div class="rounded-
|
|
2
|
-
<div class="px-
|
|
3
|
-
<
|
|
4
|
-
|
|
1
|
+
<div class="rounded-xl border border-border/60 bg-white shadow-sm overflow-hidden">
|
|
2
|
+
<div class="flex items-center gap-3 px-4 py-3 border-b border-border/40">
|
|
3
|
+
<div class="rounded-md bg-orange-50 p-1.5 flex-shrink-0">
|
|
4
|
+
<svg class="w-4 h-4 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<div>
|
|
7
|
+
<p class="text-sm font-semibold text-foreground"><%= t("ruby_cms.admin.analytics.utm_sources", default: "UTM Sources") %></p>
|
|
8
|
+
<p class="text-xs text-muted-foreground">Campaign traffic breakdown</p>
|
|
9
|
+
</div>
|
|
5
10
|
</div>
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
<%
|
|
12
|
+
<% if @utm_sources.present? && @utm_sources.any? %>
|
|
13
|
+
<% total = @utm_sources.values.sum.to_f %>
|
|
14
|
+
<div class="divide-y divide-border/30">
|
|
9
15
|
<% @utm_sources.first(6).each do |source, count| %>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
16
|
+
<% pct = total.positive? ? (count / total * 100).round(1) : 0 %>
|
|
17
|
+
<div class="relative flex items-center justify-between gap-3 px-4 py-2.5">
|
|
18
|
+
<div class="absolute inset-y-0 left-0 bg-orange-500/5" style="width: <%= pct %>%"></div>
|
|
19
|
+
<span class="relative z-10 text-sm text-foreground truncate"><%= source %></span>
|
|
20
|
+
<div class="relative z-10 flex items-center gap-2 flex-shrink-0">
|
|
21
|
+
<span class="text-xs text-muted-foreground tabular-nums"><%= pct %>%</span>
|
|
22
|
+
<span class="text-sm font-semibold tabular-nums text-foreground"><%= number_with_delimiter(count) %></span>
|
|
23
|
+
</div>
|
|
13
24
|
</div>
|
|
14
25
|
<% end %>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
</div>
|
|
27
|
+
<% else %>
|
|
28
|
+
<div class="flex flex-col items-center justify-center px-4 py-10 gap-2">
|
|
29
|
+
<svg class="w-8 h-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
|
|
30
|
+
<p class="text-sm text-muted-foreground">No UTM campaign data</p>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
21
33
|
</div>
|
|
@@ -29,8 +29,15 @@
|
|
|
29
29
|
end
|
|
30
30
|
%>
|
|
31
31
|
|
|
32
|
+
<div class="mb-4">
|
|
33
|
+
<h1 class="text-2xl font-semibold text-foreground">Content blocks</h1>
|
|
34
|
+
<p class="mt-1 text-sm text-muted-foreground">
|
|
35
|
+
<%= number_with_delimiter(@pagination&.dig(:total_count) || @content_blocks&.size || 0) %> total items
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
32
39
|
<%= render partial: "ruby_cms/admin/shared/bulk_action_table_index", locals: {
|
|
33
|
-
title:
|
|
40
|
+
title: nil,
|
|
34
41
|
collection: @content_blocks || [],
|
|
35
42
|
headers: ["Key", "Locale", "Title", "Type", "Published", { text: "Actions", class: "text-right" }],
|
|
36
43
|
action_icons: [
|