Enhances user experience with dark‑mode detection, responsive layout, and streamlined role selection workflow
143 lines
7.5 KiB
HTML
143 lines
7.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}User Contributions{% endblock %}
|
|
{% block breadcrumb %}
|
|
<nav class="mb-4" aria-label="Breadcrumb">
|
|
<ol class="flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400">
|
|
<li><a href="{% url 'dashboard' %}" class="hover:text-gray-700 dark:hover:text-gray-300">Dashboard</a></li>
|
|
<li class="text-gray-300 dark:text-gray-600">/</li>
|
|
<li class="text-gray-900 dark:text-white font-medium" aria-current="page">Contributions</li>
|
|
</ol>
|
|
</nav>
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
<div>
|
|
<h1 class="text-2xl font-bold">User Contributions</h1>
|
|
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">GitHub-style contribution heatmap</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
{% for year in available_years %}
|
|
<a href="/dashboard/contributions/?year={{ year }}" class="dk-btn-ghost text-xs px-3 py-1.5 {% if target_year == year %}bg-gray-100 dark:bg-gray-800{% endif %}">{{ year }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-6">
|
|
{% for entry in contributions %}
|
|
<div class="dk-card divide-y divide-gray-200 dark:divide-gray-600">
|
|
<div class="px-6 py-4">
|
|
<div class="flex items-center gap-3">
|
|
{% with name=entry.user.get_full_name|default:entry.user.username %}
|
|
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center text-white text-sm font-bold">{{ name|make_list|first|upper }}</div>
|
|
<div>
|
|
<p class="font-semibold text-sm">{{ name }}</p>
|
|
<p class="text-xs text-gray-400">Activity for {{ target_year }}</p>
|
|
</div>
|
|
{% endwith %}
|
|
</div>
|
|
</div>
|
|
<div class="px-6 py-4">
|
|
<div class="rounded-lg bg-gray-50 dark:bg-gray-800 p-3 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 mb-4">
|
|
<div class="flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-4">
|
|
<div class="flex-1 text-center p-2 rounded-lg">
|
|
<p class="text-lg font-bold text-gray-900 dark:text-white">{{ entry.tickets_created }}</p>
|
|
<p class="text-xs font-medium text-gray-600 dark:text-gray-400">Tickets Created</p>
|
|
</div>
|
|
<div class="flex-1 text-center p-2 rounded-lg">
|
|
<p class="text-lg font-bold text-gray-900 dark:text-white">{{ entry.status_changes }}</p>
|
|
<p class="text-xs font-medium text-gray-600 dark:text-gray-400">Status Changes</p>
|
|
</div>
|
|
<div class="flex-1 text-center p-2 rounded-lg">
|
|
<p class="text-lg font-bold text-gray-900 dark:text-white">{{ entry.comments_made }}</p>
|
|
<p class="text-xs font-medium text-gray-600 dark:text-gray-400">Comments Made</p>
|
|
</div>
|
|
<div class="flex-1 text-center p-2 rounded-lg">
|
|
<p class="text-lg font-bold text-gray-900 dark:text-white">{{ entry.active_days }}</p>
|
|
<p class="text-xs font-medium text-gray-600 dark:text-gray-400">Active Days</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">Daily Contributions</h4>
|
|
<div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<span>Less</span>
|
|
<div class="flex gap-1">
|
|
<div class="h-3 w-3 rounded-sm bg-gray-200 dark:bg-gray-800"></div>
|
|
<div class="h-3 w-3 rounded-sm" style="background-color:#9be9a8" data-dark="#0e4429"></div>
|
|
<div class="h-3 w-3 rounded-sm" style="background-color:#40c463" data-dark="#006d32"></div>
|
|
<div class="h-3 w-3 rounded-sm" style="background-color:#30a14e" data-dark="#26a641"></div>
|
|
<div class="h-3 w-3 rounded-sm" style="background-color:#216e39" data-dark="#39d353"></div>
|
|
</div>
|
|
<span>More</span>
|
|
</div>
|
|
</div>
|
|
<div class="rounded-xl bg-white dark:bg-gray-950 p-4 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10">
|
|
<div class="flex gap-[3px] overflow-x-auto">
|
|
<div class="flex flex-col gap-[3px] mr-1">
|
|
<div class="h-3 flex items-end text-[8px] text-gray-500 dark:text-gray-400 font-medium">Sun</div>
|
|
<div class="h-3 flex items-end text-[8px] text-gray-500 dark:text-gray-400 font-medium">Mon</div>
|
|
<div class="h-3 flex items-end text-[8px] text-gray-500 dark:text-gray-400 font-medium">Tue</div>
|
|
<div class="h-3 flex items-end text-[8px] text-gray-500 dark:text-gray-400 font-medium">Wed</div>
|
|
<div class="h-3 flex items-end text-[8px] text-gray-500 dark:text-gray-400 font-medium">Thu</div>
|
|
<div class="h-3 flex items-end text-[8px] text-gray-500 dark:text-gray-400 font-medium">Fri</div>
|
|
<div class="h-3 flex items-end text-[8px] text-gray-500 dark:text-gray-400 font-medium">Sat</div>
|
|
</div>
|
|
{% for week in entry.weeks %}
|
|
<div class="flex flex-col gap-[3px]">
|
|
{% for day in week %}
|
|
<div class="contribution-square h-3 w-3 rounded-sm cursor-pointer transition-all duration-150 hover:ring-2 hover:ring-primary-500/50"
|
|
style="{% if day.level == 0 %}background-color: #ebedf0{% elif day.level == 1 %}background-color: #9be9a8{% elif day.level == 2 %}background-color: #40c463{% elif day.level == 3 %}background-color: #30a14e{% else %}background-color: #216e39{% endif %}"
|
|
data-dark-style="{% if day.level == 0 %}background-color: #161b22{% elif day.level == 1 %}background-color: #0e4429{% elif day.level == 2 %}background-color: #006d32{% elif day.level == 3 %}background-color: #26a641{% else %}background-color: #39d353{% endif %}"
|
|
title="{{ day.date }}: {{ day.count }} contributions"></div>
|
|
{% endfor %}
|
|
</div>
|
|
{% empty %}
|
|
<p class="text-xs text-gray-400 py-4">No data for this period</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center py-12 text-gray-400">
|
|
<i class="fas fa-chart-bar text-4xl mb-3"></i>
|
|
<p class="text-lg font-medium">No contributions data</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
function updateContributionColors() {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
document.querySelectorAll('.contribution-square').forEach(function(el) {
|
|
if (isDark) {
|
|
el.setAttribute('style', el.getAttribute('data-dark-style') || 'background-color: #161b22');
|
|
} else {
|
|
const lightStyle = el.getAttribute('style').split(';')[0] + ';';
|
|
if (!el.hasAttribute('data-light-style')) {
|
|
el.setAttribute('data-light-style', el.getAttribute('style'));
|
|
}
|
|
el.setAttribute('style', el.getAttribute('data-light-style'));
|
|
}
|
|
});
|
|
document.querySelectorAll('[data-dark]').forEach(function(el) {
|
|
if (isDark) {
|
|
el.setAttribute('style', 'background-color: ' + el.getAttribute('data-dark'));
|
|
} else {
|
|
el.setAttribute('style', el.getAttribute('data-light') ? 'background-color: ' + el.getAttribute('data-light') : el.style.cssText.split(';')[0] + ';');
|
|
}
|
|
});
|
|
}
|
|
updateContributionColors();
|
|
new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(m) {
|
|
if (m.type === 'attributes' && m.attributeName === 'class') updateContributionColors();
|
|
});
|
|
}).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
})();
|
|
</script>
|
|
{% endblock %} |