Enhances user experience with dark‑mode detection, responsive layout, and streamlined role selection workflow
94 lines
5.7 KiB
HTML
94 lines
5.7 KiB
HTML
<div id="board-container">
|
|
<div class="mb-4 flex items-center justify-between flex-wrap gap-3">
|
|
<div class="flex items-center gap-3">
|
|
<h1 class="text-xl font-bold">{{ project.name }}</h1>
|
|
<span class="dk-badge text-white text-xs font-mono" style="background-color:{{ project.color|default:'#3B82F6' }}">{{ project.ticket_prefix }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<select x-model="sort" @change="changeSort($el.value)" class="text-sm border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-1.5 bg-white dark:bg-gray-950">
|
|
<option value="created_at_desc">Newest First</option>
|
|
<option value="created_at_asc">Oldest First</option>
|
|
<option value="name_asc">Name A-Z</option>
|
|
<option value="name_desc">Name Z-A</option>
|
|
<option value="due_date_asc">Due Date</option>
|
|
<option value="priority">Priority</option>
|
|
</select>
|
|
<a href="/dashboard/tickets/create/?project={{ project.id }}" class="dk-btn-primary text-sm"><i class="fas fa-plus"></i> New Ticket</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-4 overflow-x-auto pb-4" style="min-height: 700px;">
|
|
{% for col in columns %}
|
|
<div class="status-column shrink-0 w-[280px] max-w-[350px] bg-gray-50 dark:bg-gray-950/50 rounded-xl border border-gray-200 dark:border-gray-600 flex flex-col" data-status-id="{{ col.status.id }}"
|
|
@dragover.prevent="$el.classList.add('ring-2', 'ring-blue-500')"
|
|
@dragleave="$el.classList.remove('ring-2', 'ring-blue-500')"
|
|
@drop.prevent="
|
|
$el.classList.remove('ring-2', 'ring-blue-500');
|
|
if (!dragging) return;
|
|
const tid = dragging;
|
|
dragging = null;
|
|
const csrf = (document.cookie.split('; ').find(c => c.startsWith('csrftoken')) || '').split('=')[1];
|
|
fetch('/api/tickets/' + tid + '/move/', {
|
|
method: 'PATCH',
|
|
headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrf},
|
|
body: JSON.stringify({status_id: $el.dataset.statusId})
|
|
}).then(r => {
|
|
if (r.ok) {
|
|
const s = document.querySelector('[x-model=sort]');
|
|
htmx.ajax('GET', '/dashboard/board/{{ project.id }}/data/?sort=' + (s ? s.value : 'created_at_desc'), {target: '#board-container', swap: 'outerHTML'});
|
|
}
|
|
});
|
|
">
|
|
<div class="px-3 py-3 flex items-center justify-between border-b border-gray-200 dark:border-gray-600">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-3 h-3 rounded-full" style="background-color:{{ col.status.color }}"></div>
|
|
<span class="font-semibold text-sm">{{ col.status.name }}</span>
|
|
{% if col.status.is_completed %}<i class="fas fa-check-circle text-emerald-500 text-xs"></i>{% endif %}
|
|
<span class="dk-badge bg-gray-200 dark:bg-gray-800 text-xs">{{ col.count }}</span>
|
|
<button @click="openNewTicket({{ col.status.id }})" class="text-gray-400 hover:text-blue-500 text-xs ml-1" title="Add ticket"><i class="fas fa-plus"></i></button>
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 overflow-y-auto p-2 space-y-2" style="max-height: 650px;">
|
|
{% for ticket in col.tickets %}
|
|
<div class="ticket-card dk-card p-3 hover:shadow-md transition-shadow cursor-grab active:cursor-grabbing" data-ticket-id="{{ ticket.id }}" draggable="true"
|
|
@dragstart="dragging = $el.dataset.ticketId; $el.classList.add('opacity-50'); $event.dataTransfer.effectAllowed = 'move'; $event.dataTransfer.setData('text/plain', $el.dataset.ticketId)"
|
|
@dragend="$el.classList.remove('opacity-50'); dragging = null">
|
|
<div class="flex items-center justify-between mb-1.5">
|
|
<span class="text-xs font-mono text-gray-400">{{ ticket.uuid }}</span>
|
|
{% if ticket.priority %}
|
|
<span class="dk-badge text-xs text-white" style="background-color:{{ ticket.priority.color|default:'#6B7280' }}">{{ ticket.priority.name }}</span>
|
|
{% endif %}
|
|
</div>
|
|
<h4 class="text-sm font-medium mb-1 leading-snug">{{ ticket.name }}</h4>
|
|
{% if ticket.description %}
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2 line-clamp-2">{{ ticket.description|striptags|truncatechars:100 }}</p>
|
|
{% endif %}
|
|
<div class="flex items-center justify-between mt-2">
|
|
<div class="flex -space-x-1.5">
|
|
{% for assignee in ticket.assignees.all|slice:":3" %}
|
|
<div class="w-6 h-6 rounded-full border-2 border-white dark:border-gray-700 flex items-center justify-center text-[10px] font-bold text-white" style="background-color:{% cycle '#3B82F6' '#10B981' '#F59E0B' '#EF4444' '#8B5CF6' %}">
|
|
{{ assignee.get_full_name|default:assignee.username|make_list|first|upper }}
|
|
</div>
|
|
{% endfor %}
|
|
{% if ticket.assignees.count > 3 %}
|
|
<div class="w-6 h-6 rounded-full border-2 border-white dark:border-gray-700 bg-gray-200 dark:bg-gray-700 flex items-center justify-center text-[10px] font-bold text-gray-600 dark:text-gray-300">+{{ ticket.assignees.count|add:"-3" }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% if ticket.due_date %}
|
|
<span class="text-xs {% if ticket.due_date < today %}text-red-500{% else %}text-gray-400{% endif %}">
|
|
<i class="far fa-calendar mr-0.5"></i>{{ ticket.due_date|date:"M d" }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center py-8 text-gray-400 border-2 border-dashed border-gray-200 dark:border-gray-600 rounded-lg">
|
|
<p class="text-xs">No tickets</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|