Files
project-managment/templates/epics/overview.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

243 lines
16 KiB
HTML

{% extends "base.html" %}
{% block title %}Epics Overview{% endblock %}
{% block extra_head %}
{% include "components/rich_text_editor_assets.html" %}
{% 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">Epics</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<div x-data="{
editEpicOpen: false,
editEpicData: { id: '', name: '', description: '', sort_order: '', start_date: '', end_date: '' },
createTicketOpen: false,
createTicketData: { name: '', description: '', status_id: '', priority_id: '', start_date: '', due_date: '', epic: '', project: '' }
}">
{% if not selected_project_id %}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold">Epics Overview</h1>
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">Browse epics across all projects</p>
</div>
</div>
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 p-6">
<div class="mb-5">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">Select Project</h3>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Choose a project to view its epics</p>
</div>
<div x-data="{ search: '', visibleCount: {{ projects|length }} }" class="space-y-4">
<div class="mb-4">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
<input type="text" x-model="search" @input='visibleCount = $el.closest("[x-data]").querySelectorAll("[data-project-card]:not([style*=\"display: none\"])").length' placeholder="Search projects by name or prefix..."
class="block w-full pl-10 pr-10 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-950 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-600 focus:ring-2 focus:ring-primary-500 focus:border-transparent outline-none text-sm">
<button x-show="search" @click="search = ''; visibleCount = {{ projects|length }}" x-cloak class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
</div>
{% if not projects %}
<div class="flex flex-col items-center justify-center py-12 text-gray-500 dark:text-gray-400">
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-1">No Projects Available</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">You don't have access to any projects yet.</p>
</div>
{% else %}
<div x-show="!search || visibleCount > 0">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
{% for p in projects %}
<div data-project-card x-show="!search || '{{ p.name|lower }} {{ p.ticket_prefix|lower }}'.includes(search.toLowerCase())">
<a href="/dashboard/epics/{{ p.id }}/" class="block relative p-4 bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-600 rounded-lg hover:shadow-md transition-all overflow-hidden text-left"
style="border-left: 4px solid {{ p.color|default:'#6B7280' }};">
{% if p.pinned_date %}
<div class="absolute top-2 right-2">
<div class="flex items-center justify-center w-6 h-6 rounded-full shadow-sm" style="background-color:{{ p.color|default:'#6B7280' }};" title="Pinned Project">
<i class="fas fa-thumbtack text-white text-xs"></i>
</div>
</div>
{% endif %}
<span class="inline-flex px-2.5 py-1 rounded text-xs font-semibold mb-3 text-white" style="background-color:{{ p.color|default:'#6B7280' }}">{{ p.ticket_prefix }}</span>
<h3 class="font-semibold text-base text-gray-900 dark:text-white line-clamp-2">{{ p.name }}</h3>
</a>
</div>
{% endfor %}
</div>
</div>
<div x-show="search && visibleCount === 0" class="flex flex-col items-center justify-center py-12 text-gray-500 dark:text-gray-400">
<i class="fas fa-search text-4xl mb-3 text-gray-400 dark:text-gray-500"></i>
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-1">No Projects Found</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">Try adjusting your search terms</p>
</div>
{% endif %}
</div>
</div>
{% else %}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold">Epics Overview</h1>
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">{{ selected_project.name }}</p>
</div>
<div class="flex items-center gap-2">
<a href="{% url 'epics-overview' %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-arrow-left mr-1"></i> Back</a>
</div>
</div>
<div>
<div class="mb-4" x-data="{ open: false }">
<div class="relative">
<button @click="open = !open" @click.away="open = false"
class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-950 border-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
style="border-color: {{ selected_project.color|default:'#D1D5DB' }};">
{% if selected_project.ticket_prefix %}
<span class="px-2 py-0.5 rounded text-xs font-semibold text-white" style="background-color:{{ selected_project.color|default:'#6B7280' }}">{{ selected_project.ticket_prefix }}</span>
{% endif %}
<span>{{ selected_project.name }}</span>
<i class="fas fa-chevron-down text-xs transition-transform" :class="{ 'rotate-180': open }"></i>
</button>
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
class="absolute top-full left-0 mt-2 w-80 bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg z-50 max-h-96 overflow-y-auto" style="display: none;">
<div class="p-2">
<div class="px-3 py-2 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Switch Project</div>
{% for p in projects %}
<a href="/dashboard/epics/{{ p.id }}/" @click="open = false"
class="w-full flex items-center gap-3 px-3 py-2 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors text-left {% if p.id == selected_project.id %}bg-gray-50 dark:bg-gray-800{% endif %}">
{% if p.pinned_date %}
<div class="flex items-center justify-center w-5 h-5 rounded-full shrink-0" style="background-color:{{ p.color|default:'#6B7280' }};" title="Pinned">
<i class="fas fa-thumbtack text-white text-[8px]"></i>
</div>
{% endif %}
{% if p.ticket_prefix %}
<span class="px-2 py-0.5 rounded text-xs font-semibold text-white flex-shrink-0" style="background-color:{{ p.color|default:'#6B7280' }}">{{ p.ticket_prefix }}</span>
{% endif %}
<div class="flex-1 min-w-0 text-sm font-medium text-gray-900 dark:text-white truncate">{{ p.name }}</div>
{% if p.id == selected_project.id %}
<i class="fas fa-check flex-shrink-0" style="color:{{ p.color|default:'#3B82F6' }}"></i>
{% endif %}
</a>
{% endfor %}
</div>
</div>
</div>
</div>
{% if epics %}
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10">
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-600">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">Epics Overview</h3>
</div>
<div class="grid grid-cols-1 gap-4">
{% for epic in epics %}
<div class="bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 rounded-lg overflow-hidden">
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-600 flex items-center justify-between gap-4">
<div class="flex items-center gap-3 min-w-0">
<div>
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">{{ epic.name }}</h3>
<div class="text-xs text-gray-500 dark:text-gray-400">
{{ epic.start_date|date:"M d, Y"|default:"-" }} — {{ epic.end_date|date:"M d, Y"|default:"-" }}
</div>
</div>
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300">
{{ epic.tickets.count }} tickets
</span>
<button type="button" @click="editEpicData = { id: '{{ epic.id }}', name: '{{ epic.name|escapejs }}', description: '{{ epic.description|escapejs }}', sort_order: '{{ epic.sort_order }}', start_date: '{{ epic.start_date|date:'Y-m-d'|default:'' }}', end_date: '{{ epic.end_date|date:'Y-m-d'|default:'' }}' }; editEpicOpen = true" class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-lg border border-blue-200 dark:border-blue-800 text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30">Edit</button>
</div>
</div>
<div>
{% if epic.description %}
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-600 bg-gray-50 dark:bg-gray-800/30">
<div class="text-sm text-gray-600 dark:text-gray-400 prose prose-sm dark:prose-invert max-w-none">{{ epic.description|safe }}</div>
</div>
{% endif %}
<div class="w-full">
<div class="px-6 py-3 border-b border-gray-100 dark:border-gray-600 flex items-center justify-between">
<h4 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Tickets</h4>
<button type="button" @click="createTicketData.epic = '{{ epic.id }}'; createTicketData.project = '{{ epic.project.id }}'; createTicketOpen = true" class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-lg border border-blue-200 dark:border-blue-800 text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30"><i class="fas fa-plus mr-1"></i>Add Ticket</button>
</div>
{% if epic.tickets.all %}
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr class="border-b border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800">
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">ID</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Ticket</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden sm:table-cell">Assign To</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider hidden md:table-cell">Due Date</th>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
{% for ticket in epic.tickets.all %}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors">
<td class="px-4 py-3 whitespace-nowrap text-sm font-mono text-gray-500 dark:text-gray-400">{{ ticket.uuid|truncatechars:8 }}</td>
<td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white">{{ ticket.name }}</td>
<td class="px-4 py-3 whitespace-nowrap text-sm">
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-semibold" style="color:{{ ticket.status.color }};background-color:{{ ticket.status.color }}20">
{{ ticket.status.name }}
</span>
</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 hidden sm:table-cell">
{% if ticket.assignees.all %}
<div class="flex flex-wrap gap-1">
{% for a in ticket.assignees.all|slice:":2" %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300">{{ a.get_full_name|default:a.username }}</span>
{% endfor %}
{% if ticket.assignees.all|length > 2 %}
<span class="relative inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 cursor-default" x-data="{ tooltip: false }" @mouseenter="tooltip = true" @mouseleave="tooltip = false">
+{{ ticket.assignees.all|length|add:"-2" }}
<div x-show="tooltip" x-cloak x-transition class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 bg-gray-900 dark:bg-gray-700 text-white text-xs rounded whitespace-nowrap z-50">
{% for a in ticket.assignees.all|slice:"2:" %}{{ a.get_full_name|default:a.username }}{% if not forloop.last %}, {% endif %}{% endfor %}
</div>
</span>
{% endif %}
</div>
{% else %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400">Unassigned</span>
{% endif %}
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 hidden md:table-cell">{{ ticket.due_date|date:"M d, Y"|default:"-" }}</td>
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
<a href="{% url 'ticket-detail' ticket.id %}" target="_blank" class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-lg border border-blue-200 dark:border-blue-800 text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30">View</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-sm text-gray-500 dark:text-gray-400 py-8 text-center">No tickets found for this epic.</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div class="flex flex-col items-center justify-center h-64 text-gray-500 dark:text-gray-400 gap-4">
<div class="flex items-center justify-center rounded-full bg-gray-100 dark:bg-gray-950 p-6">
<i class="fas fa-flag text-4xl text-gray-400 dark:text-gray-500"></i>
</div>
<h2 class="text-xl font-medium text-gray-600 dark:text-gray-300">No epics found in this project</h2>
<p class="text-sm text-gray-500 dark:text-gray-400">This project doesn't have any epics yet. Create an epic to organize your tickets.</p>
<a href="{% url 'epic-create-project' selected_project.id %}" class="dk-btn dk-btn-primary text-sm"><i class="fas fa-plus"></i> Create Epic</a>
</div>
{% endif %}
</div>
{% endif %}
{% include "components/edit_epic_modal.html" %}
{% include "components/create_ticket_modal.html" %}
</div>
{% endblock %}