notebook/app/views/data/usage.html.erb
2025-06-17 11:34:50 -05:00

515 lines
22 KiB
Plaintext

<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex items-center justify-between mb-8">
<div class="flex items-center">
<%= link_to data_vault_path, class: 'text-gray-500 hover:text-gray-700 mr-3', title: "Back to your Data Vault" do %>
<i class="material-icons">arrow_back</i>
<% end %>
<h1 class="text-3xl font-bold text-gray-700">Notebook Analytics</h1>
</div>
<div class="text-sm text-gray-500">
Member since <%= current_user.created_at.strftime("%B %Y") %>
</div>
</div>
<!-- Key Metrics Dashboard -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg shadow-md p-6 text-white">
<div class="flex justify-between items-start">
<div>
<p class="text-blue-100 text-sm font-medium">Total Pages</p>
<h3 class="text-4xl font-bold mt-1">
<%= number_with_delimiter @content.values.sum(&:count) %>
</h3>
</div>
<div class="bg-white/20 rounded-full p-2">
<i class="material-icons">auto_stories</i>
</div>
</div>
<div class="mt-4 text-sm text-blue-100">
<%
recent_pages = 0
@content.each do |_, pages|
recent_pages += pages.select { |page| page.created_at > 30.days.ago }.count
end
%>
<% if recent_pages > 0 %>
<span class="font-medium"><%= recent_pages %> new</span> in the last 30 days
<% else %>
No new pages in the last 30 days
<% end %>
</div>
</div>
<div class="bg-gradient-to-br from-purple-500 to-purple-600 rounded-lg shadow-md p-6 text-white">
<div class="flex justify-between items-start">
<div>
<p class="text-purple-100 text-sm font-medium">Total Updates</p>
<h3 class="text-4xl font-bold mt-1">
<%= number_with_delimiter current_user.content_change_events.count %>
</h3>
</div>
<div class="bg-white/20 rounded-full p-2">
<i class="material-icons">edit</i>
</div>
</div>
<div class="mt-4 text-sm text-purple-100">
<% recent_updates = current_user.content_change_events.where('created_at > ?', 30.days.ago).count %>
<% if recent_updates > 0 %>
<span class="font-medium"><%= recent_updates %> updates</span> in the last 30 days
<% else %>
No updates in the last 30 days
<% end %>
</div>
</div>
<div class="bg-gradient-to-br from-green-500 to-green-600 rounded-lg shadow-md p-6 text-white">
<div class="flex justify-between items-start">
<div>
<p class="text-green-100 text-sm font-medium">Active Days</p>
<h3 class="text-4xl font-bold mt-1">
<%= number_with_delimiter current_user.content_change_events.group_by_day(:created_at).count %>
</h3>
</div>
<div class="bg-white/20 rounded-full p-2">
<i class="material-icons">calendar_today</i>
</div>
</div>
<div class="mt-4 text-sm text-green-100">
<%
streak_days = 0
current_date = Date.today
while current_user.content_change_events.where('DATE(created_at) = ?', current_date).exists?
streak_days += 1
current_date = current_date - 1.day
end
%>
<% if streak_days > 0 %>
Current streak: <span class="font-medium"><%= streak_days %> days</span>
<% else %>
No current streak
<% end %>
</div>
</div>
<div class="bg-gradient-to-br from-amber-500 to-amber-600 rounded-lg shadow-md p-6 text-white">
<div class="flex justify-between items-start">
<div>
<p class="text-amber-100 text-sm font-medium">Most Active Content</p>
<%
# Find most active content type based on number of pages
most_active_type = nil
most_active_count = 0
@content.each do |type, pages|
if pages.count > most_active_count
most_active_type = type
most_active_count = pages.count
end
end
%>
<h3 class="text-2xl font-bold mt-1 truncate">
<%= most_active_type.pluralize %>
</h3>
</div>
<div class="bg-white/20 rounded-full p-2">
<i class="material-icons">trending_up</i>
</div>
</div>
<div class="mt-4 text-sm text-amber-100">
<span class="font-medium"><%= number_with_delimiter most_active_count %> pages</span> of this type
</div>
</div>
</div>
<!-- Activity Trends -->
<div class="bg-white rounded-lg shadow-md overflow-hidden mb-8">
<div class="p-6 border-b border-gray-200">
<div class="flex justify-between items-center">
<div>
<h2 class="text-xl font-semibold text-gray-800 mb-1">Activity Trends</h2>
<p class="text-gray-600">
Track how your worldbuilding activity changes over time
</p>
</div>
<div class="text-sm text-gray-500">
Every field edit counts as one update
</div>
</div>
</div>
<div class="border-b border-gray-200">
<div class="flex overflow-x-auto">
<a href="#edits-this-week" class="px-4 py-2 font-medium text-sm border-b-2 border-blue-500 text-blue-600 whitespace-nowrap tab-link" data-tab="edits-this-week">
This week
</a>
<a href="#edits-this-month" class="px-4 py-2 font-medium text-sm border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap tab-link" data-tab="edits-this-month">
This month
</a>
<a href="#edits-this-year" class="px-4 py-2 font-medium text-sm border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap tab-link" data-tab="edits-this-year">
This year
</a>
<a href="#edits-all-time" class="px-4 py-2 font-medium text-sm border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap tab-link" data-tab="edits-all-time">
All time
</a>
</div>
</div>
<div class="p-6 bg-gray-50">
<div id="edits-this-week" class="tab-content">
<% weekly_data = current_user.content_change_events.where('created_at > ?', DateTime.now - 7.days).group_by_day(:created_at).map { |date, count| [date.split(' ').first, count] } %>
<%= area_chart weekly_data, download: true, colors: ["#3b82f6"], points: false %>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
<div class="text-sm text-gray-500 mb-1">Total Updates</div>
<div class="text-2xl font-bold text-gray-800"><%= number_with_delimiter weekly_data.sum { |_, count| count } %></div>
</div>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
<div class="text-sm text-gray-500 mb-1">Most Active Day</div>
<% most_active = weekly_data.max_by { |_, count| count } %>
<div class="text-2xl font-bold text-gray-800">
<%= most_active ? Date.parse(most_active[0]).strftime("%A") : "N/A" %>
</div>
</div>
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
<div class="text-sm text-gray-500 mb-1">Daily Average</div>
<div class="text-2xl font-bold text-gray-800">
<%= weekly_data.any? ? (weekly_data.sum { |_, count| count }.to_f / weekly_data.size).round : 0 %>
</div>
</div>
</div>
</div>
<div id="edits-this-month" class="tab-content hidden">
<% monthly_data = current_user.content_change_events.where('created_at > ?', DateTime.now - 1.month).group_by_week(:created_at).map { |date, count| [date.split(' ').first, count] } %>
<%= area_chart monthly_data, download: true, colors: ["#8b5cf6"] %>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-purple-50 rounded-lg p-4 border border-purple-100">
<div class="text-sm text-gray-500 mb-1">Total Updates</div>
<div class="text-2xl font-bold text-gray-800"><%= number_with_delimiter monthly_data.sum { |_, count| count } %></div>
</div>
<div class="bg-purple-50 rounded-lg p-4 border border-purple-100">
<div class="text-sm text-gray-500 mb-1">Most Active Week</div>
<% most_active = monthly_data.max_by { |_, count| count } %>
<div class="text-2xl font-bold text-gray-800">
<%= most_active ? "Week of #{Date.parse(most_active[0]).strftime("%b %d")}" : "N/A" %>
</div>
</div>
<div class="bg-purple-50 rounded-lg p-4 border border-purple-100">
<div class="text-sm text-gray-500 mb-1">Weekly Average</div>
<div class="text-2xl font-bold text-gray-800">
<%= monthly_data.any? ? (monthly_data.sum { |_, count| count }.to_f / monthly_data.size).round : 0 %>
</div>
</div>
</div>
</div>
<div id="edits-this-year" class="tab-content hidden">
<% yearly_data = current_user.content_change_events.where('created_at > ?', DateTime.now - 1.year).group_by_month(:created_at).map { |date, count| [date.split(' ').first, count] } %>
<%= area_chart yearly_data, download: true, colors: ["#10b981"] %>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-green-50 rounded-lg p-4 border border-green-100">
<div class="text-sm text-gray-500 mb-1">Total Updates</div>
<div class="text-2xl font-bold text-gray-800"><%= number_with_delimiter yearly_data.sum { |_, count| count } %></div>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-100">
<div class="text-sm text-gray-500 mb-1">Most Active Month</div>
<% most_active = yearly_data.max_by { |_, count| count } %>
<div class="text-2xl font-bold text-gray-800">
<%= most_active ? Date.parse(most_active[0]).strftime("%B") : "N/A" %>
</div>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-100">
<div class="text-sm text-gray-500 mb-1">Monthly Average</div>
<div class="text-2xl font-bold text-gray-800">
<%= yearly_data.any? ? (yearly_data.sum { |_, count| count }.to_f / yearly_data.size).round : 0 %>
</div>
</div>
</div>
</div>
<div id="edits-all-time" class="tab-content hidden">
<% all_time_data = current_user.content_change_events.group_by_month(:created_at).map { |date, count| [date.split(' ').first, count] } %>
<%= area_chart all_time_data, download: true, colors: ["#f59e0b"] %>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
<div class="text-sm text-gray-500 mb-1">Total Updates</div>
<div class="text-2xl font-bold text-gray-800"><%= number_with_delimiter all_time_data.sum { |_, count| count } %></div>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
<div class="text-sm text-gray-500 mb-1">Most Active Month</div>
<% most_active = all_time_data.max_by { |_, count| count } %>
<div class="text-2xl font-bold text-gray-800">
<%= most_active ? Date.parse(most_active[0]).strftime("%B %Y") : "N/A" %>
</div>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
<div class="text-sm text-gray-500 mb-1">Active Months</div>
<div class="text-2xl font-bold text-gray-800">
<%= all_time_data.count %>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Content Distribution -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- Content Categories -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="p-6 border-b border-gray-200">
<h2 class="text-xl font-semibold text-gray-800 mb-1">Content Categories</h2>
<p class="text-gray-600">
Your content organized by category
</p>
</div>
<div class="p-6 bg-gray-50">
<%
# Group content types into categories
categories = {
"Characters" => ["Character", "Person"],
"Locations" => ["Location", "Town", "Country", "Landmark", "Building", "Region", "Planet"],
"Items" => ["Item", "Creature", "Flora", "Vehicle", "Magic", "Technology"],
"Story Elements" => ["Scene", "Group", "Tradition", "Government", "Religion", "Race", "Deity", "Language"],
"Other" => []
}
category_counts = {}
@content.each do |type, pages|
found_category = false
categories.each do |category, types|
if types.include?(type)
category_counts[category] ||= 0
category_counts[category] += pages.count
found_category = true
break
end
end
unless found_category
category_counts["Other"] ||= 0
category_counts["Other"] += pages.count
end
end
# Remove empty categories
category_counts.delete_if { |_, count| count == 0 }
%>
<%= pie_chart category_counts, download: true, legend: "right", donut: true %>
<div class="mt-6 space-y-4">
<% category_counts.sort_by { |_, count| -count }.each do |category, count| %>
<div class="flex items-center">
<div class="w-1/3 font-medium text-gray-700"><%= category %></div>
<div class="w-2/3">
<div class="flex items-center">
<div class="flex-grow h-2 bg-gray-200 rounded-full overflow-hidden">
<div class="h-full bg-blue-600" style="width: <%= (count.to_f / category_counts.values.sum * 100).round %>%"></div>
</div>
<span class="ml-2 text-sm text-gray-600"><%= count %> (<%= (count.to_f / category_counts.values.sum * 100).round %>%)</span>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<!-- Top Content Types -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="p-6 border-b border-gray-200">
<h2 class="text-xl font-semibold text-gray-800 mb-1">Top Content Types</h2>
<p class="text-gray-600">
Your most frequently created content types
</p>
</div>
<div class="p-6 bg-gray-50">
<%
# Get top 10 content types by count
top_content_types = @content.sort_by { |_, pages| -pages.count }.first(10).to_h
%>
<%= bar_chart top_content_types.map { |n, l| [n.pluralize(l.count), l.count] }, download: true, colors: ["#3b82f6"] %>
<div class="mt-6">
<div class="grid grid-cols-2 gap-4">
<% top_content_types.each_with_index do |(type, pages), index| %>
<% break if index >= 10 %>
<div class="flex items-center p-3 rounded-lg <%= index < 3 ? 'bg-blue-50 border border-blue-100' : '' %>">
<div class="mr-3 text-xl font-bold <%= index < 3 ? 'text-blue-600' : 'text-gray-400' %>"><%= index + 1 %></div>
<div>
<div class="font-medium text-gray-800"><%= type.pluralize(pages.count) %></div>
<div class="text-sm text-gray-500"><%= pages.count %> <%= 'page'.pluralize(pages.count) %></div>
</div>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
<!-- Content Creation Timeline -->
<div class="bg-white rounded-lg shadow-md overflow-hidden mb-8">
<div class="p-6 border-b border-gray-200">
<h2 class="text-xl font-semibold text-gray-800 mb-1">Content Creation Timeline</h2>
<p class="text-gray-600">
When you created different types of content
</p>
</div>
<div class="p-6 bg-gray-50">
<%
# Get creation timeline data
timeline_data = {}
@content.each do |type, pages|
next if pages.empty?
# Group pages by month manually
month_groups = pages.group_by { |page| page.created_at.beginning_of_month }
month_groups.each do |month_date, month_pages|
date_str = month_date.strftime("%Y-%m")
timeline_data[date_str] ||= {}
timeline_data[date_str][type] = month_pages.count
end
end
# Convert to format needed for line chart
chart_data = []
top_5_types = @content.sort_by { |_, pages| -pages.count }.first(5).map(&:first)
top_5_types.each do |type|
data_points = []
timeline_data.sort_by { |date, _| date }.each do |date, counts|
data_points << [date, counts[type] || 0]
end
chart_data << {name: type, data: data_points}
end
%>
<%= line_chart chart_data, download: true %>
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-100 rounded-lg p-4">
<h3 class="font-medium text-gray-800 mb-2">First Content Created</h3>
<%
first_content = nil
first_date = nil
@content.each do |type, pages|
next if pages.empty?
earliest = pages.min_by(&:created_at)
if first_date.nil? || earliest.created_at < first_date
first_content = earliest
first_date = earliest.created_at
end
end
%>
<% if first_content %>
<div class="text-gray-600">
Your first page was a <strong><%= first_content.class.name %></strong> named
"<span class="text-blue-600"><%= first_content.name %></span>"
created on <%= first_date.strftime("%B %d, %Y") %>
</div>
<% else %>
<div class="text-gray-600">You haven't created any content yet.</div>
<% end %>
</div>
<div class="bg-gray-100 rounded-lg p-4">
<h3 class="font-medium text-gray-800 mb-2">Most Recent Content</h3>
<%
latest_content = nil
latest_date = nil
@content.each do |type, pages|
next if pages.empty?
newest = pages.max_by(&:created_at)
if latest_date.nil? || newest.created_at > latest_date
latest_content = newest
latest_date = newest.created_at
end
end
%>
<% if latest_content %>
<div class="text-gray-600">
Your most recent page is a <strong><%= latest_content.class.name %></strong> named
"<span class="text-blue-600"><%= latest_content.name %></span>"
created <%= time_ago_in_words(latest_date) %> ago
</div>
<% else %>
<div class="text-gray-600">You haven't created any content yet.</div>
<% end %>
</div>
</div>
</div>
</div>
<!-- Link to Achievements Page -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="p-6 flex items-center justify-between">
<div>
<h2 class="text-xl font-semibold text-gray-800 mb-1">Achievements & Milestones</h2>
<p class="text-gray-600">
Track your progress and unlock achievements as you build your world
</p>
</div>
<div>
<%= link_to achievements_path, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" do %>
<i class="material-icons mr-1">emoji_events</i>
View Achievements
<% end %>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle tab clicks
document.querySelectorAll('.tab-link').forEach(tabLink => {
tabLink.addEventListener('click', function(e) {
e.preventDefault();
const tabId = this.getAttribute('data-tab');
const tabGroup = this.closest('.border-b').nextElementSibling;
// Update tab links
this.closest('.flex').querySelectorAll('.tab-link').forEach(link => {
if (link === this) {
link.classList.add('border-blue-500', 'text-blue-600');
link.classList.remove('border-transparent', 'text-gray-500');
} else {
link.classList.remove('border-blue-500', 'text-blue-600');
link.classList.add('border-transparent', 'text-gray-500');
}
});
// Show the selected tab content and hide others
tabGroup.querySelectorAll('.tab-content').forEach(content => {
if (content.id === tabId) {
content.classList.remove('hidden');
} else {
content.classList.add('hidden');
}
});
});
});
});
</script>