Files
project-managment/templates/projects/list.html
T
sankalp 457fc76f9d feat(templates/projects): add manage statuses button and restricted mode UI
Provides admin UI to manage statuses and prevents moving tickets to completed statuses
2026-06-28 19:15:35 +05:45

252 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Projects{% 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">Projects</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<div x-data="{ columns: {{ columns|safe }}, openCols: false, selectedIds: [], selectAll: false }">
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 class="text-2xl font-bold">Projects</h1>
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">{{ page_obj.paginator.count }} total projects</p>
</div>
<div class="flex items-center gap-2">
{% if request.user.is_admin %}
<a href="{% url 'project-create' %}" class="dk-btn dk-btn-primary text-sm"><i class="fas fa-plus"></i> New Project</a>
{% endif %}
<form class="flex gap-2" method="get">
{% for c in columns %}<input type="hidden" name="columns" value="{{ c }}">{% endfor %}
{% if per_page != 10 %}<input type="hidden" name="per_page" value="{{ per_page }}">{% endif %}
<input type="text" name="search" value="{{ search }}" placeholder="Search by name or prefix..."
class="text-sm border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 bg-white dark:bg-gray-950 focus:outline-none focus:ring-2 focus:ring-blue-500 w-56">
<button type="submit" class="dk-btn dk-btn-primary text-sm"><i class="fas fa-search"></i> Search</button>
{% if search %}
<a href="{% url 'projects-list' %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-times"></i> Clear</a>
{% endif %}
</form>
</div>
</div>
<form id="projects-bulk-delete-form" method="post" action="{% url 'projects-bulk-delete' %}">
{% csrf_token %}
<template x-for="id in selectedIds" x-key="id">
<input type="hidden" name="ids" :value="id">
</template>
</form>
<div x-show="selectedIds.length > 0" x-cloak class="mb-4 flex items-center gap-3 px-4 py-2.5 bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-lg">
<span class="text-sm font-medium text-primary-700 dark:text-primary-300">
<span x-text="selectedIds.length"></span> selected
</span>
<button type="button" @click="confirmAction({title:'Delete Projects', message:'Delete ' + selectedIds.length + ' project(s)?', confirmText:'Delete', danger:true, onConfirm:function(){ document.getElementById('projects-bulk-delete-form').submit() }})" class="dk-btn dk-btn-danger text-xs px-3 py-1">
<i class="fas fa-trash mr-1"></i> Delete Selected
</button>
<button type="button" @click="selectedIds = []; selectAll = false" class="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 ml-auto">
<i class="fas fa-times mr-1"></i> Clear selection
</button>
</div>
<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 flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-3">
<a href="{% url 'projects-list' %}" class="text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"><i class="fas fa-sync-alt mr-1"></i> Reset</a>
</div>
<div class="flex items-center gap-3">
<div class="relative">
<button @click="openCols = !openCols" class="text-xs flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300">
<i class="fas fa-columns text-[10px]"></i> Columns
</button>
<div x-show="openCols" @click.outside="openCols = false" x-cloak class="absolute right-0 mt-1 w-44 bg-white dark:bg-gray-950 rounded-lg shadow-lg ring-1 ring-gray-950/5 dark:ring-white/10 z-50 py-1">
<form method="get" id="columns-form">
<input type="hidden" name="search" value="{{ search }}">
{% if per_page != 10 %}<input type="hidden" name="per_page" value="{{ per_page }}">{% endif %}
{% for c in all_columns %}
<label class="flex items-center gap-2 px-3 py-1.5 text-xs text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer">
<input type="checkbox" name="columns" value="{{ c }}" {% if c in columns %}checked{% endif %} onchange="document.getElementById('columns-form').submit()" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
{{ c|title }}
</label>
{% endfor %}
</form>
</div>
</div>
<select name="per_page" onchange="this.form.submit()" form="columns-form" class="text-xs border border-gray-200 dark:border-gray-600 rounded-lg px-2 py-1.5 bg-white dark:bg-gray-950 text-gray-700 dark:text-gray-300">
<option value="5" {% if per_page == 5 %}selected{% endif %}>5 per page</option>
<option value="10" {% if per_page == 10 %}selected{% endif %}>10 per page</option>
<option value="25" {% if per_page == 25 %}selected{% endif %}>25 per page</option>
<option value="50" {% if per_page == 50 %}selected{% endif %}>50 per page</option>
</select>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-100 dark:border-gray-600 bg-gray-50 dark:bg-gray-800">
<th class="px-3 py-3 w-10">
<input type="checkbox" @change="selectAll = !selectAll; selectedIds = selectAll ? Array.from(document.querySelectorAll('.project-checkbox')).map(cb => cb.value) : []" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
</th>
{% if "color" in columns %}<th class="px-2 py-3 w-10"></th>{% endif %}
{% if "name" in columns %}<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<a href="?sort=name&order={% if sort == 'name' and order == 'asc' %}desc{% else %}asc{% endif %}{% if search %}&search={{ search }}{% endif %}{% for c in columns %}&columns={{ c }}{% endfor %}&per_page={{ per_page }}" class="hover:text-gray-700 dark:hover:text-gray-300">
Name {% if sort == 'name' %}<i class="fas fa-sort-{{ order }}-alt text-[10px]"></i>{% endif %}
</a>
</th>{% endif %}
{% if "ticket_prefix" in columns %}<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Prefix</th>{% endif %}
{% if "progress" in columns %}<th class="px-4 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Progress</th>{% endif %}
{% if "start_date" in columns %}<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<a href="?sort=start_date&order={% if sort == 'start_date' and order == 'asc' %}desc{% else %}asc{% endif %}{% if search %}&search={{ search }}{% endif %}{% for c in columns %}&columns={{ c }}{% endfor %}&per_page={{ per_page }}" class="hover:text-gray-700 dark:hover:text-gray-300">
Start Date {% if sort == 'start_date' %}<i class="fas fa-sort-{{ order }}-alt text-[10px]"></i>{% endif %}
</a>
</th>{% endif %}
{% if "end_date" in columns %}<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">End Date</th>{% endif %}
{% if "remaining_days" in columns %}<th class="px-4 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Remaining Days</th>{% endif %}
{% if "pinned" in columns %}<th class="px-4 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Pinned</th>{% endif %}
{% if "members" in columns %}<th class="px-4 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<a href="?sort=members&order={% if sort == 'members' and order == 'asc' %}desc{% else %}asc{% endif %}{% if search %}&search={{ search }}{% endif %}{% for c in columns %}&columns={{ c }}{% endfor %}&per_page={{ per_page }}" class="hover:text-gray-700 dark:hover:text-gray-300">
Members {% if sort == 'members' %}<i class="fas fa-sort-{{ order }}-alt text-[10px]"></i>{% endif %}
</a>
</th>{% endif %}
{% if "tickets" in columns %}<th class="px-4 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<a href="?sort=tickets&order={% if sort == 'tickets' and order == 'asc' %}desc{% else %}asc{% endif %}{% if search %}&search={{ search }}{% endif %}{% for c in columns %}&columns={{ c }}{% endfor %}&per_page={{ per_page }}" class="hover:text-gray-700 dark:hover:text-gray-300">
Tickets {% if sort == 'tickets' %}<i class="fas fa-sort-{{ order }}-alt text-[10px]"></i>{% endif %}
</a>
</th>{% endif %}
{% if "created_at" in columns %}<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Created</th>{% endif %}
{% if "updated_at" in columns %}<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Updated</th>{% endif %}
<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 p in projects %}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors" :class="selectedIds.includes('{{ p.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
<td class="px-3 py-3">
<input type="checkbox" class="project-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" value="{{ p.id }}" x-model="selectedIds">
</td>
{% if "color" in columns %}
<td class="px-2 py-3">
<div class="w-6 h-6 rounded" style="background-color:{{ p.color|default:'#6B7280' }}"></div>
</td>
{% endif %}
{% if "name" in columns %}
<td class="px-4 py-3">
<a href="{% url 'project-detail' p.id %}" class="font-medium text-gray-900 dark:text-white hover:text-primary">{{ p.name }}</a>
</td>
{% endif %}
{% if "ticket_prefix" in columns %}
<td class="px-4 py-3">
<span class="inline-flex px-2 py-0.5 rounded text-xs font-semibold text-white" style="background-color:{{ p.color|default:'#3B82F6' }}">{{ p.ticket_prefix }}</span>
</td>
{% endif %}
{% if "progress" in columns %}
<td class="px-4 py-3 text-center">
{% with prog=p.progress_percentage %}
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
{% if prog >= 100 %}bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400
{% elif prog >= 75 %}bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400
{% elif prog >= 50 %}bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400
{% elif prog >= 25 %}bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300
{% else %}bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400{% endif %}">
{{ prog }}%
</span>
{% endwith %}
</td>
{% endif %}
{% if "start_date" in columns %}
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ p.start_date|date:"d/m/Y"|default:"—" }}</td>
{% endif %}
{% if "end_date" in columns %}
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ p.end_date|date:"d/m/Y"|default:"—" }}</td>
{% endif %}
{% if "remaining_days" in columns %}
<td class="px-4 py-3 text-center">
{% if p.remaining_days is None %}
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400">—</span>
{% else %}
{% with days=p.remaining_days %}
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
{% if days <= 0 %}bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400
{% elif days <= 7 %}bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400
{% else %}bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400{% endif %}">
{{ days }} days
</span>
{% endwith %}
{% endif %}
</td>
{% endif %}
{% if "pinned" in columns %}
<td class="px-4 py-3 text-center">
<form method="post" action="{% url 'project-toggle-pin' p.id %}">
{% csrf_token %}
<button type="submit" onclick="event.preventDefault(); this.closest('form').submit()" role="switch"
class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
style="background-color:{% if p.pinned_date %}var(--color-primary, #3b82f6){% else %}#d1d5db{% endif %};">
<span class="pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-0 transition duration-200 ease-in-out"
style="transform:{% if p.pinned_date %}translateX(16px){% else %}translateX(0){% endif %};"></span>
</button>
</form>
</td>
{% endif %}
{% if "members" in columns %}
<td class="px-4 py-3 text-center text-sm font-medium text-gray-900 dark:text-white">{{ p.member_count }}</td>
{% endif %}
{% if "tickets" in columns %}
<td class="px-4 py-3 text-center text-sm font-medium text-gray-900 dark:text-white">{{ p.ticket_count }}</td>
{% endif %}
{% if "created_at" in columns %}
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ p.created_at|date:"d/m/Y H:i" }}</td>
{% endif %}
{% if "updated_at" in columns %}
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{{ p.updated_at|date:"d/m/Y H:i" }}</td>
{% endif %}
<td class="px-4 py-3 text-right">
<div class="flex items-center justify-end gap-1">
<a href="{% url 'project-detail' p.id %}" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">View</a>
<a href="{% url 'project-edit' p.id %}" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">Edit</a>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="20" class="text-center py-12 text-gray-400">
<i class="fas fa-folder-open text-4xl mb-3 block text-gray-300 dark:text-gray-600"></i>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">No projects yet</p>
{% if request.user.is_admin %}
<a href="{% url 'project-create' %}" class="text-primary text-sm mt-2 inline-block hover:underline">Create your first project</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if page_obj.has_other_pages %}
<div class="px-6 py-3 border-t border-gray-100 dark:border-gray-600 flex items-center justify-between">
<p class="text-xs text-gray-500 dark:text-gray-400">
Showing {{ page_obj.start_index }}–{{ page_obj.end_index }} of {{ page_obj.paginator.count }}
</p>
<div class="flex items-center gap-1">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}{% if search %}&search={{ search }}{% endif %}{% for c in columns %}&columns={{ c }}{% endfor %}{% if per_page != 10 %}&per_page={{ per_page }}{% endif %}&sort={{ sort }}&order={{ order }}" class="px-2.5 py-1 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800">Previous</a>
{% endif %}
{% for n in page_obj.paginator.page_range %}
{% if n == page_obj.number %}
<span class="px-2.5 py-1 rounded text-xs font-medium bg-blue-600 text-white">{{ n }}</span>
{% elif n >= page_obj.number|add:"-2" and n <= page_obj.number|add:"2" %}
<a href="?page={{ n }}{% if search %}&search={{ search }}{% endif %}{% for c in columns %}&columns={{ c }}{% endfor %}{% if per_page != 10 %}&per_page={{ per_page }}{% endif %}&sort={{ sort }}&order={{ order }}" class="px-2.5 py-1 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800">{{ n }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}{% if search %}&search={{ search }}{% endif %}{% for c in columns %}&columns={{ c }}{% endfor %}{% if per_page != 10 %}&per_page={{ per_page }}{% endif %}&sort={{ sort }}&order={{ order }}" class="px-2.5 py-1 rounded text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800">Next</a>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}