Files
project-managment/templates/dashboard/index.html
T
sankalp 71b700388b feat(templates): add polished login UI with Tailwind and Alpine.js
Enhances user experience with dark‑mode detection, responsive layout, and streamlined role selection workflow
2026-06-28 14:15:39 +05:45

109 lines
7.4 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Dashboard{% 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 class="text-gray-900 dark:text-white font-medium" aria-current="page">Dashboard</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<!-- Welcome Banner -->
<div class="mb-6 rounded-xl p-6 shadow-sm" style="background: linear-gradient(135deg, var(--color-primary, #3b82f6) 0%, var(--color-primary-hover, #2563eb) 100%); color: #ffffff;">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 class="text-xl sm:text-2xl font-bold"><span id="greeting-text">Welcome back</span>, {{ request.user.get_full_name|default:request.user.username }}!</h1>
<p id="dashboard-quote" class="text-blue-100 text-sm mt-1.5 italic font-medium">Loading quote...</p>
</div>
<div class="text-right">
<p class="text-2xl font-bold">{% now "d" %}</p>
<p class="text-blue-100 text-xs uppercase tracking-wider">{% now "M Y" %}</p>
</div>
</div>
</div>
<script>
fetch('{% static "quotes.json" %}')
.then(res => res.json())
.then(data => {
if (data && data.quotes && data.quotes.length > 0) {
const quotesList = data.quotes;
const randomQuote = quotesList[Math.floor(Math.random() * quotesList.length)];
document.getElementById('dashboard-quote').textContent = `"${randomQuote.quote}" — ${randomQuote.author}`;
} else {
throw new Error('Invalid structure');
}
})
.catch(() => {
const fallbackQuotes = [
"The only way to do great work is to love what you do. — Steve Jobs",
"Quality is not an act, it is a habit. — Aristotle",
"Action is the foundational key to all success. — Pablo Picasso",
"Strive not to be a success, but rather to be of value. — Albert Einstein",
"The secret of getting ahead is getting started. — Mark Twain"
];
const randomQuote = fallbackQuotes[Math.floor(Math.random() * fallbackQuotes.length)];
document.getElementById('dashboard-quote').textContent = randomQuote;
});
</script>
<!-- Stats Cards -->
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4 mb-6">
{% for stat in stats %}
<div class="group relative rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 p-5 transition-all duration-200 hover:shadow-md hover:-translate-y-0.5">
{% if stat.super %}
<div class="absolute top-4 right-4">
<span class="inline-flex items-center rounded-full bg-blue-50 dark:bg-blue-900/30 px-2 py-0.5 text-[10px] font-semibold text-blue-600 dark:text-blue-400 uppercase tracking-wider">Admin</span>
</div>
{% endif %}
<div class="flex items-center gap-3 mb-3">
<div class="flex h-10 w-10 items-center justify-center rounded-lg
{% if stat.color == 'primary' %}bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400
{% elif stat.color == 'success' %}bg-emerald-100 dark:bg-emerald-900/50 text-emerald-600 dark:text-emerald-400
{% elif stat.color == 'info' %}bg-cyan-100 dark:bg-cyan-900/50 text-cyan-600 dark:text-cyan-400
{% elif stat.color == 'danger' %}bg-red-100 dark:bg-red-900/50 text-red-600 dark:text-red-400
{% elif stat.color == 'warning' %}bg-amber-100 dark:bg-amber-900/50 text-amber-600 dark:text-amber-400
{% else %}bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400{% endif %}">
<i class="fas {{ stat.icon }} text-lg"></i>
</div>
<span class="text-sm font-medium text-gray-600 dark:text-gray-400">{{ stat.label }}</span>
</div>
<p class="text-3xl font-bold tracking-tight text-gray-900 dark:text-white">{{ stat.value }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ stat.desc }}</p>
</div>
{% endfor %}
</div>
<!-- Charts Row 1 -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6 mb-6">
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10" hx-get="/dashboard/charts/tickets-per-project/" hx-trigger="load, every[60s]" hx-swap="outerHTML"><div class="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-600"><div><h3 class="text-base font-semibold text-gray-900 dark:text-white">Tickets per Project</h3><p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Distribution across projects</p></div></div><div class="p-6"><div class="h-[280px] animate-pulse bg-gray-100 dark:bg-gray-800 rounded"></div></div></div>
<div class="xl:col-span-2 rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10" hx-get="/dashboard/charts/monthly-trend/" hx-trigger="load, every[60s]" hx-swap="outerHTML"><div class="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-600"><div><h3 class="text-base font-semibold text-gray-900 dark:text-white">Monthly Trend</h3><p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Ticket creation over time</p></div></div><div class="p-6"><div class="h-[280px] animate-pulse bg-gray-100 dark:bg-gray-800 rounded"></div></div></div>
</div>
<!-- Charts Row 2 + Recent Activity -->
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6 mb-6">
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10" hx-get="/dashboard/charts/user-statistics/" hx-trigger="load" hx-swap="outerHTML"><div class="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-600"><div><h3 class="text-base font-semibold text-gray-900 dark:text-white">User Statistics</h3><p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Individual contributions</p></div></div><div class="p-6"><div class="h-[280px] animate-pulse bg-gray-100 dark:bg-gray-800 rounded"></div></div></div>
<div class="xl:col-span-2 rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10" hx-get="/dashboard/recent-activity/" hx-trigger="load, every[30s]" hx-swap="outerHTML"><div class="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-600"><div><h3 class="text-base font-semibold text-gray-900 dark:text-white">Recent Activity</h3><p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Latest updates across projects</p></div></div><div class="p-6"><div class="h-[280px] animate-pulse bg-gray-100 dark:bg-gray-800 rounded"></div></div></div>
</div>
<!-- Project Timeline -->
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 mb-6" hx-get="/dashboard/project-timeline-widget/" hx-trigger="load, every[60s]" hx-swap="outerHTML"><div class="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-600"><div><h3 class="text-base font-semibold text-gray-900 dark:text-white">Project Timeline</h3><p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Upcoming milestones and deadlines</p></div></div><div class="p-6"><div class="h-20 animate-pulse bg-gray-100 dark:bg-gray-800 rounded"></div></div></div>
{% endblock %}
{% block extra_scripts %}
<script>
(function() {
var el = document.getElementById('greeting-text');
if (!el) return;
var hour = new Date().getHours();
var g;
if (hour >= 5 && hour < 12) g = 'Good morning';
else if (hour >= 12 && hour < 17) g = 'Good afternoon';
else if (hour >= 17 && hour < 21) g = 'Good evening';
else g = 'Good night';
el.textContent = g;
})();
</script>
{% endblock %}