mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Update chart style
This commit is contained in:
Binary file not shown.
@@ -76,7 +76,7 @@
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
{% for tag in link.tags.all %}
|
||||
{% with number=forloop.counter %}
|
||||
<a href="{% url 'tag-detail' tag.slug %}"
|
||||
<a href="{% url 'tag-detail' tag.slug %}"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium transition duration-150 {% if number|divisibleby:10 %}tag-10{% elif number|divisibleby:9 %}tag-9{% elif number|divisibleby:8 %}tag-8{% elif number|divisibleby:7 %}tag-7{% elif number|divisibleby:6 %}tag-6{% elif number|divisibleby:5 %}tag-5{% elif number|divisibleby:4 %}tag-4{% elif number|divisibleby:3 %}tag-3{% elif number|divisibleby:2 %}tag-2{% else %}tag-1{% endif %}">
|
||||
{{ tag.name }}
|
||||
</a>
|
||||
@@ -105,10 +105,28 @@
|
||||
|
||||
<div class="bg-white shadow-md rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
|
||||
<h2 class="text-xl font-bold text-gray-900">{% trans "Click Statistics" %}</h2>
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center space-y-4 sm:space-y-0">
|
||||
<h2 class="text-xl font-bold text-gray-900">{% trans "Click Statistics" %} - {{ period_name }}</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button onclick="changePeriod('3m')" class="period-btn {% if current_period == '3m' %}active{% endif %}" data-period="3m">
|
||||
{% trans "3 Months" %}
|
||||
</button>
|
||||
<button onclick="changePeriod('6m')" class="period-btn {% if current_period == '6m' %}active{% endif %}" data-period="6m">
|
||||
{% trans "6 Months" %}
|
||||
</button>
|
||||
<button onclick="changePeriod('1y')" class="period-btn {% if current_period == '1y' %}active{% endif %}" data-period="1y">
|
||||
{% trans "1 Year" %}
|
||||
</button>
|
||||
<button onclick="changePeriod('all')" class="period-btn {% if current_period == 'all' %}active{% endif %}" data-period="all">
|
||||
{% trans "All Time" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<canvas id="clickChart"></canvas>
|
||||
<div style="height: 400px; position: relative;">
|
||||
<canvas id="clickChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -144,34 +162,87 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var ctx = document.getElementById('clickChart').getContext('2d');
|
||||
var clickData = {{ click_stats|safe }};
|
||||
var intervalDays = {{ interval_days }};
|
||||
|
||||
var chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: clickData.map(item => item.date),
|
||||
labels: clickData.map(item => {
|
||||
// Use custom label if available, otherwise format date based on interval
|
||||
if (item.label) {
|
||||
return item.label;
|
||||
}
|
||||
|
||||
const date = new Date(item.date);
|
||||
if (intervalDays === 1) {
|
||||
// Daily: show "Jan 15"
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
} else if (intervalDays <= 14) {
|
||||
// Weekly/Bi-weekly: show "1/15"
|
||||
return date.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
|
||||
} else {
|
||||
// Monthly: show "Jan 2025"
|
||||
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
|
||||
}
|
||||
}),
|
||||
datasets: [{
|
||||
label: '{% trans "Clicks" %}',
|
||||
data: clickData.map(item => item.count),
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
borderWidth: 1
|
||||
backgroundColor: 'rgba(99, 102, 241, 0.1)',
|
||||
borderColor: 'rgba(99, 102, 241, 1)',
|
||||
borderWidth: 3,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointBackgroundColor: 'rgba(99, 102, 241, 1)',
|
||||
pointBorderColor: '#ffffff',
|
||||
pointBorderWidth: 2,
|
||||
pointRadius: 4,
|
||||
pointHoverRadius: 6,
|
||||
pointHoverBackgroundColor: 'rgba(99, 102, 241, 1)',
|
||||
pointHoverBorderColor: '#ffffff',
|
||||
pointHoverBorderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: '{% trans "Number of Clicks" %}'
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)',
|
||||
borderDash: [2, 2]
|
||||
},
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
color: '#6b7280',
|
||||
font: {
|
||||
size: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
x: {
|
||||
title: {
|
||||
display: true,
|
||||
text: '{% trans "Date" %}'
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
color: '#6b7280',
|
||||
font: {
|
||||
size: 12
|
||||
},
|
||||
maxTicksLimit: intervalDays === 1 ? 15 : 10
|
||||
}
|
||||
}
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
borderJoinStyle: 'round'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -200,6 +271,13 @@
|
||||
tag.classList.add(...randomColor.split(' '));
|
||||
});
|
||||
});
|
||||
|
||||
// Function to change time period
|
||||
function changePeriod(period) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('period', period);
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -271,6 +349,40 @@
|
||||
padding-right: 1.1428571em;
|
||||
padding-bottom: 0.8571429em;
|
||||
padding-left: 1.1428571em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Chart container styling */
|
||||
#clickChart {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Period selection buttons */
|
||||
.period-btn {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid #d1d5db;
|
||||
background-color: #ffffff;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.period-btn:hover {
|
||||
background-color: #f9fafb;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.period-btn.active {
|
||||
background-color: #6366f1;
|
||||
border-color: #6366f1;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.period-btn.active:hover {
|
||||
background-color: #5b21b6;
|
||||
border-color: #5b21b6;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
+92
-5
@@ -271,16 +271,103 @@ class LinkDetailView(DetailView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
click_stats = ClickLog.objects.filter(link=self.object).annotate(
|
||||
from datetime import datetime, timedelta
|
||||
import calendar
|
||||
|
||||
# Get the time period from request (default to 3 months)
|
||||
period = self.request.GET.get('period', '3m')
|
||||
|
||||
# Calculate date range and interval based on period
|
||||
end_date = datetime.now().date()
|
||||
if period == '3m':
|
||||
start_date = end_date - timedelta(days=89) # 90 days total
|
||||
period_name = "3 Months"
|
||||
interval_days = 1 # Daily
|
||||
elif period == '6m':
|
||||
start_date = end_date - timedelta(days=179) # 180 days total
|
||||
period_name = "6 Months"
|
||||
interval_days = 7 # Weekly
|
||||
elif period == '1y':
|
||||
start_date = end_date - timedelta(days=364) # 365 days total
|
||||
period_name = "1 Year"
|
||||
interval_days = 7 # Weekly
|
||||
elif period == 'all':
|
||||
# Get the earliest click date, or default to 1 year ago if no clicks
|
||||
earliest_click = ClickLog.objects.filter(link=self.object).order_by('clicked_at').first()
|
||||
if earliest_click:
|
||||
start_date = earliest_click.clicked_at.date()
|
||||
# Determine interval based on data range
|
||||
days_range = (end_date - start_date).days
|
||||
if days_range <= 90:
|
||||
interval_days = 7 # Weekly
|
||||
elif days_range <= 365:
|
||||
interval_days = 7 # Weekly
|
||||
else:
|
||||
interval_days = 30 # Monthly
|
||||
else:
|
||||
start_date = end_date - timedelta(days=364)
|
||||
interval_days = 7 # Weekly
|
||||
period_name = "All Time"
|
||||
else:
|
||||
start_date = end_date - timedelta(days=89) # Default to 3 months
|
||||
period_name = "3 Months"
|
||||
interval_days = 1 # Daily
|
||||
|
||||
# Get click stats from database
|
||||
click_stats = ClickLog.objects.filter(
|
||||
link=self.object,
|
||||
clicked_at__date__gte=start_date,
|
||||
clicked_at__date__lte=end_date
|
||||
).annotate(
|
||||
date=TruncDate('clicked_at')
|
||||
).values('date').annotate(count=Count('id')).order_by('date')
|
||||
|
||||
# Convert query results to list and format dates as strings
|
||||
click_stats_list = list(click_stats)
|
||||
for item in click_stats_list:
|
||||
item['date'] = item['date'].strftime('%Y-%m-%d')
|
||||
# Convert to dictionary for easy lookup
|
||||
click_dict = {item['date']: item['count'] for item in click_stats}
|
||||
|
||||
# Create complete dataset with appropriate intervals
|
||||
click_stats_list = []
|
||||
current_date = start_date
|
||||
|
||||
if interval_days == 1:
|
||||
# Daily intervals
|
||||
while current_date <= end_date:
|
||||
click_stats_list.append({
|
||||
'date': current_date.strftime('%Y-%m-%d'),
|
||||
'count': click_dict.get(current_date, 0)
|
||||
})
|
||||
current_date += timedelta(days=1)
|
||||
else:
|
||||
# Weekly, bi-weekly, or monthly intervals
|
||||
while current_date <= end_date:
|
||||
interval_end = min(current_date + timedelta(days=interval_days - 1), end_date)
|
||||
|
||||
# Sum clicks for this interval
|
||||
interval_count = 0
|
||||
temp_date = current_date
|
||||
while temp_date <= interval_end:
|
||||
interval_count += click_dict.get(temp_date, 0)
|
||||
temp_date += timedelta(days=1)
|
||||
|
||||
# Format label based on interval
|
||||
if interval_days == 7: # Weekly
|
||||
label = f"{current_date.strftime('%m/%d')}"
|
||||
elif interval_days == 14: # Bi-weekly
|
||||
label = f"{current_date.strftime('%m/%d')}"
|
||||
else: # Monthly
|
||||
label = f"{calendar.month_abbr[current_date.month]} {current_date.year}"
|
||||
|
||||
click_stats_list.append({
|
||||
'date': current_date.strftime('%Y-%m-%d'),
|
||||
'count': interval_count,
|
||||
'label': label
|
||||
})
|
||||
current_date += timedelta(days=interval_days)
|
||||
|
||||
context['click_stats'] = json.dumps(click_stats_list, cls=DjangoJSONEncoder)
|
||||
context['current_period'] = period
|
||||
context['period_name'] = period_name
|
||||
context['interval_days'] = interval_days
|
||||
|
||||
# Convert markdown to HTML if the link is a custom type
|
||||
if self.object.link_type == Link.LinkType.CUSTOM:
|
||||
|
||||
Reference in New Issue
Block a user