Files
project-managment/templates/projects/form.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

1061 lines
86 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 %}{% if project %}Edit {{ project.name }}{% else %}Create Project{% endif %}{% 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-sm text-gray-500 dark:text-gray-400">
<li><a href="{% url 'projects-list' %}" class="hover:text-gray-700 dark:hover:text-gray-300">Projects</a></li>
<li class="text-gray-300 dark:text-gray-600"><i class="fas fa-chevron-right text-[10px]"></i></li>
{% if project %}
<li><a href="{% url 'project-detail' project.id %}" class="hover:text-gray-700 dark:hover:text-gray-300">{{ project.name }}</a></li>
<li class="text-gray-300 dark:text-gray-600"><i class="fas fa-chevron-right text-[10px]"></i></li>
<li class="text-gray-900 dark:text-white font-medium" aria-current="page">Edit</li>
{% else %}
<li class="text-gray-900 dark:text-white font-medium" aria-current="page">Create</li>
{% endif %}
</ol>
</nav>
{% endblock %}
{% block content %}
<div x-data="{
externalModalOpen: false, activeTab: new URLSearchParams(window.location.search).has('add_member') ? 'members' : 'details',
editStatusOpen: false,
editStatusData: { id: '', name: '', color: '', sort_order: '', is_completed: false },
createStatusOpen: false,
createStatusData: { name: '', color: '#6B7280', sort_order: '0', is_completed: false },
createMemberOpen: new URLSearchParams(window.location.search).has('add_member'),
createMemberData: { user_id: '' },
createUserOpen: false,
createNoteOpen: false,
createNoteData: { title: '', content: '' },
editNoteOpen: false,
editNoteData: { id: '', title: '', content: '' },
editEpicOpen: false,
editEpicData: { id: '', name: '', description: '', sort_order: '', start_date: '', end_date: '' },
createEpicOpen: false,
createEpicData: { name: '', description: '', sort_order: '0', start_date: '', end_date: '' },
editTicketOpen: false,
editTicketData: { id: '', name: '', description: '', status_id: '', priority_id: '', start_date: '', due_date: '' },
createTicketOpen: false,
createTicketData: { name: '', description: '', status_id: '', priority_id: '', start_date: '', due_date: '' },
selectedMemberIds: [], selectAllMembers: false,
selectedEpicIds: [], selectAllEpics: false,
selectedTicketIds: [], selectAllTickets: false,
selectedNoteIds: [], selectAllNotes: false,
selectedStatusIds: [], selectAllStatuses: false
}">
<div class="w-full">
<div class="mb-6 flex items-center justify-between">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white tracking-tight">{% if project %}Edit Project{% else %}Create Project{% endif %}</h1>
<div class="flex items-center gap-2">
{% if project %}
<button type="button" @click="externalModalOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm 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"><i class="fas fa-globe"></i> External Dashboard</button>
<form method="post" action="{% url 'project-delete' project.id %}" class="inline">
{% csrf_token %}
<button type="button" @click="confirmAction({title:'Delete Project', message:'Delete this project and all its tickets?', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm"><i class="fas fa-trash"></i> Delete</button>
</form>
{% endif %}
</div>
</div>
<form method="post" action="{% if project %}{% url 'project-edit' project.id %}{% else %}{% url 'project-create' %}{% endif %}" >
{% csrf_token %}
<div class="space-y-6">
<!-- Full width section -->
<div class="space-y-6">
<div>
<label for="id_name" class="block text-sm font-semibold text-gray-900 dark:text-gray-100 mb-1.5">Name <span class="text-red-500">*</span></label>
<input type="text" id="id_name" name="name" value="{{ project.name|default:'' }}" required
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none transition-colors">
</div>
<div>
<label for="id_description" class="block text-sm font-semibold text-gray-900 dark:text-gray-100 mb-1.5">Description</label>
{% include "components/rich_text_editor.html" with input_name="description" initial_value=project.description %}
</div>
</div>
<!-- 2 Column Section -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="id_ticket_prefix" class="block text-sm font-semibold text-gray-900 dark:text-gray-100 mb-1.5">Ticket prefix <span class="text-red-500">*</span></label>
<input type="text" id="id_ticket_prefix" name="ticket_prefix" value="{{ project.ticket_prefix|default:'' }}" maxlength="10"
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none transition-colors">
</div>
<div>
<label for="id_color" class="block text-sm font-semibold text-gray-900 dark:text-gray-100 mb-1.5">Project Color</label>
<div class="flex items-center gap-2" x-data="{ color: '{{ project.color|default:'#3b82f6' }}' }">
<toolcool-color-picker popup-position="left" :color="color" @change="color = $event.detail.hex" class="shrink-0"></toolcool-color-picker>
<input type="text" id="id_color" name="color" x-model="color"
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none uppercase font-mono">
</div>
<p class="text-xs text-gray-500 mt-1.5">Choose a color for the project card and badge</p>
</div>
<div>
<label for="id_start_date" class="block text-sm font-semibold text-gray-900 dark:text-gray-100 mb-1.5">Start Date</label>
<input type="date" id="id_start_date" name="start_date" value="{{ project.start_date|date:'Y-m-d'|default:'' }}"
onclick="this.showPicker()"
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none transition-colors">
</div>
<div>
<label for="id_end_date" class="block text-sm font-semibold text-gray-900 dark:text-gray-100 mb-1.5">End Date</label>
<input type="date" id="id_end_date" name="end_date" value="{{ project.end_date|date:'Y-m-d'|default:'' }}"
onclick="this.showPicker()"
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none transition-colors">
</div>
{% if not project %}
<div x-data="{ useDefaultStatuses: true }">
<input type="hidden" name="use_default_statuses" :value="useDefaultStatuses ? '1' : '0'">
<label class="flex items-start gap-3 cursor-pointer" @click.prevent="useDefaultStatuses = !useDefaultStatuses">
<div role="switch" class="mt-0.5 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="useDefaultStatuses ? 'background-color: var(--color-primary, #3b82f6)' : 'background-color: #d1d5db'">
<span class="pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-0 transition-transform duration-200 ease-in-out" :style="useDefaultStatuses ? 'transform: translateX(16px)' : 'transform: translateX(0)'"></span>
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">Use Default Ticket Statuses</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Create standard Backlog, To Do, In Progress, Review, and Done statuses automatically</p>
</div>
</label>
</div>
{% else %}
<div></div>
{% endif %}
<div x-data="{ isPinned: {{ project.pinned_date|yesno:'true,false,false' }} }">
<input type="hidden" name="is_pinned" :value="isPinned ? '1' : '0'">
<label class="flex items-start gap-3 cursor-pointer" @click.prevent="isPinned = !isPinned">
<div role="switch" class="mt-0.5 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="isPinned ? 'background-color: var(--color-primary, #3b82f6)' : 'background-color: #d1d5db'">
<span class="pointer-events-none relative inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-0 transition-transform duration-200 ease-in-out" :style="isPinned ? 'transform: translateX(16px)' : 'transform: translateX(0)'"></span>
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">Pin Project</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Pinned projects will appear in the dashboard timeline</p>
</div>
</label>
</div>
</div>
</div>
<div class="flex items-center gap-3 mt-8">
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm px-4 py-2 rounded-lg transition-colors">
{% if project %}Save Changes{% else %}Create{% endif %}
</button>
{% if not project %}
<button type="submit" name="create_another" value="1" class="bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-900 dark:text-white font-medium text-sm px-4 py-2 rounded-lg transition-colors">
Create & create another
</button>
{% endif %}
<a href="{% url 'projects-list' %}" class="bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-900 dark:text-white font-medium text-sm px-4 py-2 rounded-lg transition-colors">Cancel</a>
</div>
</form>
</div>
{% if project %}
<!-- ── Relation Sections (edit page) ──────────────────────────────────── -->
<div class="max-w-full space-y-6 mt-8" x-data="{ activeTab: 'statuses' }">
{% include "components/project_tabs.html" with active_tab_var="activeTab" %}
{% if statuses %}
<div x-show="activeTab === 'statuses'" x-cloak class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-600 flex flex-wrap items-center justify-between gap-4">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">Ticket Statuses</h3>
<button type="button" @click="createStatusData = { name: '', color: '#6B7280', sort_order: '0', is_completed: false }; createStatusOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm"><i class="fas fa-plus"></i> New Status</button>
</div>
<form id="statuses-bulk-delete-form" method="post" action="{% url 'ticket-statuses-bulk-delete' %}">
{% csrf_token %}
<input type="hidden" name="project_id" value="{{ project.id }}">
<template x-for="id in selectedStatusIds" x-key="id">
<input type="hidden" name="ids" :value="id">
</template>
</form>
<div x-show="selectedStatusIds.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="selectedStatusIds.length"></span> selected
</span>
<button type="button" @click="confirmAction({title:'Delete Statuses', message:'Delete ' + selectedStatusIds.length + ' status(es)? This cannot be undone.', confirmText:'Delete', danger:true, onConfirm:function(){ document.getElementById('statuses-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="selectedStatusIds = []; selectAllStatuses = 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="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th class="px-4 py-3 w-10">
<input type="checkbox" @change="selectAllStatuses = !selectAllStatuses; selectedStatusIds = selectAllStatuses ? Array.from(document.querySelectorAll('.status-checkbox')).map(cb => cb.value) : []" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status Name</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Color</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Order</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Completed Status</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-600">
{% for s in statuses %}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="selectedStatusIds.includes('{{ s.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
<td class="px-4 py-3">
<input type="checkbox" class="status-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" :value="'{{ s.id }}'" x-model="selectedStatusIds">
</td>
<td class="px-4 py-3 text-gray-900 dark:text-white font-medium">{{ s.name }}</td>
<td class="px-4 py-3"><span class="inline-flex items-center gap-1.5 text-xs"><span class="w-3 h-3 rounded-full inline-block" style="background-color:{{ s.color }}"></span>{{ s.color }}</span></td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ s.sort_order }}</td>
<td class="px-4 py-3">{% if s.is_completed %}<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400">Yes</span>{% else %}<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400">No</span>{% endif %}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<button type="button" @click="editStatusData = {id: '{{ s.id }}', name: '{{ s.name|escapejs }}', color: '{{ s.color|escapejs }}', sort_order: '{{ s.sort_order }}', is_completed: {{ s.is_completed|lower }}}; editStatusOpen = true" 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 mr-2">Edit</button>
<form method="post" action="{% url 'ticket-status-delete' s.id %}" class="inline">
{% csrf_token %}
<input type="hidden" name="project_id" value="{{ project.id }}">
<button type="button" @click="confirmAction({title:'Delete Status', message:'Are you sure you want to delete this status? This cannot be undone.', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm">Delete</button>
</form>
</td>
</tr>
{% empty %}
<tr><td colspan="6" class="px-4 py-8 text-center text-sm text-gray-400">No statuses found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div x-show="activeTab === 'members'" x-cloak class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-600 flex flex-wrap items-center justify-between gap-4">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">Members</h3>
<div class="flex items-center gap-2">
<form method="get" class="flex items-center gap-2">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-xs"></i>
<input type="text" name="members_search" value="{{ members_search }}" placeholder="Search members..." class="pl-8 pr-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-950 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
</div>
{% if members_search %}<a href="{% url 'project-edit' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
</form>
<button type="button" @click="createMemberData = { user_id: '' }; createMemberOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm"><i class="fas fa-plus"></i> Add Member</button>
</div>
</div>
<form id="members-bulk-delete-form" method="post" action="{% url 'project-member-bulk-delete' %}">
{% csrf_token %}
<input type="hidden" name="user_id" value="{{ project.id }}">
<template x-for="id in selectedMemberIds" x-key="id">
<input type="hidden" name="member_ids" :value="id">
</template>
</form>
<div x-show="selectedMemberIds.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="selectedMemberIds.length"></span> selected
</span>
<button type="button" @click="confirmAction({title:'Remove Members', message:'Remove ' + selectedMemberIds.length + ' member(s)?', confirmText:'Remove', danger:true, onConfirm:function(){ document.getElementById('members-bulk-delete-form').submit() }})" class="dk-btn dk-btn-danger text-xs px-3 py-1">
<i class="fas fa-trash mr-1"></i> Remove Selected
</button>
<button type="button" @click="selectedMemberIds = []; selectAllMembers = 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="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th class="px-4 py-3 w-10">
<input type="checkbox" @change="selectAllMembers = !selectAllMembers; selectedMemberIds = selectAllMembers ? Array.from(document.querySelectorAll('.member-checkbox')).map(cb => cb.value) : []" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Name</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Email</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Joined</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-600">
{% for pm in members %}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="selectedMemberIds.includes('{{ pm.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
<td class="px-4 py-3">
<input type="checkbox" class="member-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" :value="'{{ pm.id }}'" x-model="selectedMemberIds">
</td>
<td class="px-4 py-3">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center text-xs font-semibold text-blue-600 dark:text-blue-400 flex-shrink-0">{{ pm.user.get_full_name|default:pm.user.username|first|upper }}</div>
<div>
<p class="font-medium text-gray-900 dark:text-white">{{ pm.user.get_full_name|default:pm.user.username }}</p>
<p class="text-xs text-gray-500">@{{ pm.user.username }}</p>
</div>
</div>
</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ pm.user.email|default:"-" }}</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ pm.created_at|date:"d/m/Y" }}</td></td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<form method="post" action="{% url 'project-member-delete' pm.id %}" class="inline">
{% csrf_token %}
<button type="button" @click="confirmAction({title:'Remove Member', message:'Remove this member from the project?', confirmText:'Remove', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm">Delete</button>
</form>
</td>
</tr>
{% empty %}
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">No members found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if epics %}
<div x-show="activeTab === 'epics'" x-cloak class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-600 flex flex-wrap items-center justify-between gap-4">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">Epics</h3>
<div class="flex items-center gap-2">
<form method="get" class="flex items-center gap-2">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-xs"></i>
<input type="text" name="epics_search" value="{{ epics_search }}" placeholder="Search epics..." class="pl-8 pr-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-950 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
</div>
<input type="hidden" name="epics_per_page" value="{{ epics_per_page }}">
{% if epics_search %}<a href="{% url 'project-edit' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
</form>
<button type="button" @click="createEpicData = { name: '', description: '', sort_order: '0', start_date: '', end_date: '' }; createEpicOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm"><i class="fas fa-plus"></i> New Epic</button>
</div>
</div>
<form id="epics-bulk-delete-form" method="post" action="{% url 'epics-bulk-delete' %}">
{% csrf_token %}
<template x-for="id in selectedEpicIds" x-key="id">
<input type="hidden" name="ids" :value="id">
</template>
</form>
<div x-show="selectedEpicIds.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="selectedEpicIds.length"></span> selected
</span>
<button type="button" @click="confirmAction({title:'Delete Epics', message:'Delete ' + selectedEpicIds.length + ' epic(s)?', confirmText:'Delete', danger:true, onConfirm:function(){ document.getElementById('epics-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="selectedEpicIds = []; selectAllEpics = 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="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th class="px-4 py-3 w-10">
<input type="checkbox" @change="selectAllEpics = !selectAllEpics; selectedEpicIds = selectAllEpics ? Array.from(document.querySelectorAll('.epic-checkbox')).map(cb => cb.value) : []" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Name</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Sort Order</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Start Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">End Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Tickets</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-600">
{% for e in epics %}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="selectedEpicIds.includes('{{ e.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
<td class="px-4 py-3">
<input type="checkbox" class="epic-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" :value="'{{ e.id }}'" x-model="selectedEpicIds">
</td>
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ e.name }}</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ e.sort_order }}</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ e.start_date|date:"d/m/Y"|default:"-" }}</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ e.end_date|date:"d/m/Y"|default:"-" }}</td>
<td class="px-4 py-3"><span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400">{{ e.ticket_count }}</span></td>
<td class="px-4 py-3">
<div class="flex items-center gap-1.5">
<button type="button" @click="editEpicData = {id: '{{ e.id }}', name: '{{ e.name|escapejs }}', description: '{{ e.description|escapejs }}', sort_order: '{{ e.sort_order }}', start_date: '{{ e.start_date|date:'Y-m-d' }}', end_date: '{{ e.end_date|date:'Y-m-d' }}'}; editEpicOpen = true" 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</button>
<form method="post" action="{% url 'epic-delete' e.id %}" class="inline">{% csrf_token %}<button type="button" @click="confirmAction({title:'Delete Epic', message:'Delete this epic?', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm">Delete</button></form>
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="7" class="px-4 py-8 text-center text-sm text-gray-400">No epics found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% if epics_paginator.paginator.num_pages > 1 %}
<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">Showing {{ epics.start_index }}–{{ epics.end_index }} of {{ epics.paginator.count }}</p>
<div class="flex items-center gap-1">
<form method="get" style="display:inline">
{% if epics_search %}<input type="hidden" name="epics_search" value="{{ epics_search }}">{% endif %}
<select name="epics_per_page" onchange="this.form.submit()" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-950">
<option value="10" {% if epics_per_page == 10 %}selected{% endif %}>10</option>
<option value="20" {% if epics_per_page == 20 %}selected{% endif %}>20</option>
<option value="50" {% if epics_per_page == 50 %}selected{% endif %}>50</option>
</select>
</form>
<div class="flex items-center gap-1 ml-2">
{% if epics.has_previous %}
<a href="?epics_page=1{% if epics_search %}&epics_search={{ epics_search }}{% endif %}&epics_per_page={{ epics_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&laquo;</a>
<a href="?epics_page={{ epics.previous_page_number }}{% if epics_search %}&epics_search={{ epics_search }}{% endif %}&epics_per_page={{ epics_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&lsaquo;</a>
{% endif %}
<span class="px-2 py-1 text-xs text-gray-500">Page {{ epics.number }}/{{ epics.paginator.num_pages }}</span>
{% if epics.has_next %}
<a href="?epics_page={{ epics.next_page_number }}{% if epics_search %}&epics_search={{ epics_search }}{% endif %}&epics_per_page={{ epics_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&rsaquo;</a>
<a href="?epics_page={{ epics.paginator.num_pages }}{% if epics_search %}&epics_search={{ epics_search }}{% endif %}&epics_per_page={{ epics_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&raquo;</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
{% if full_tickets %}
<div x-show="activeTab === 'tickets'" x-cloak class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-600 flex flex-wrap items-center justify-between gap-4">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">Tickets</h3>
<div class="flex items-center gap-2 flex-wrap">
<form method="get" class="flex items-center gap-2">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-xs"></i>
<input type="text" name="tickets_search" value="{{ tickets_search }}" placeholder="Search tickets..." class="pl-8 pr-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-950 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
</div>
{% if tickets_search or tickets_status or tickets_priority %}<a href="{% url 'project-edit' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
</form>
<form method="get" class="flex items-center gap-2">
<select name="tickets_status" onchange="this.form.submit()" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2 py-1.5 bg-white dark:bg-gray-950">
<option value="">All Statuses</option>
{% for s in statuses %}<option value="{{ s.id }}" {% if tickets_status == s.id|stringformat:"s" %}selected{% endif %}>{{ s.name }}</option>{% endfor %}
</select>
<input type="hidden" name="tickets_per_page" value="{{ tickets_per_page }}">
</form>
<form method="get" class="flex items-center gap-2">
<select name="tickets_priority" onchange="this.form.submit()" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2 py-1.5 bg-white dark:bg-gray-950">
<option value="">All Priorities</option>
{% for p in priorities %}<option value="{{ p.id }}" {% if tickets_priority == p.id|stringformat:"s" %}selected{% endif %}>{{ p.name }}</option>{% endfor %}
</select>
<input type="hidden" name="tickets_per_page" value="{{ tickets_per_page }}">
</form>
<div x-data="{ open: false }" class="relative">
<button @click="open = !open" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2.5 py-1.5 bg-white dark:bg-gray-950 hover:bg-gray-50 dark:hover:bg-gray-800 flex items-center gap-1.5"><i class="fas fa-columns"></i> Columns</button>
<div x-cloak x-show="open" @click.away="open = false" class="absolute right-0 mt-1 w-48 bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg z-20 py-1">
<form method="get">
{% for col in all_ticket_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/50 cursor-pointer">
<input type="checkbox" name="tickets_columns" value="{{ col }}" {% if col in tickets_columns %}checked{% endif %} class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" @change="$event.target.closest('form').submit()">
{{ col|title }}
</label>
{% endfor %}
</form>
</div>
</div>
<a href="{% url 'tickets-import' %}?project={{ project.id }}" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-green-600 rounded-lg hover:bg-green-700 transition-colors shadow-sm"><i class="fas fa-file-excel"></i> Import from Excel</a>
<button type="button" @click="createTicketData = { name: '', description: '', status_id: '', priority_id: '', start_date: '', due_date: '' }; createTicketOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm"><i class="fas fa-plus"></i> New Ticket</button>
</div>
</div>
<form id="tickets-bulk-delete-form" method="post" action="{% url 'tickets-bulk-delete' %}">
{% csrf_token %}
<template x-for="id in selectedTicketIds" x-key="id">
<input type="hidden" name="ids" :value="id">
</template>
</form>
<div x-show="selectedTicketIds.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="selectedTicketIds.length"></span> selected
</span>
<button type="button" @click="confirmAction({title:'Delete Tickets', message:'Delete ' + selectedTicketIds.length + ' ticket(s)?', confirmText:'Delete', danger:true, onConfirm:function(){ document.getElementById('tickets-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="selectedTicketIds = []; selectAllTickets = 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="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th class="px-4 py-3 w-10">
<input type="checkbox" @change="selectAllTickets = !selectAllTickets; selectedTicketIds = selectAllTickets ? Array.from(document.querySelectorAll('.ticket-checkbox')).map(cb => cb.value) : []" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
</th>
{% if "uuid" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">UUID</th>{% endif %}
{% if "name" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Name</th>{% endif %}
{% if "status" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>{% endif %}
{% if "priority" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Priority</th>{% endif %}
{% if "assignees" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Assignees</th>{% endif %}
{% if "start_date" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Start Date</th>{% endif %}
{% if "due_date" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Due Date</th>{% endif %}
{% if "created_at" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Created</th>{% endif %}
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-600">
{% for t in tickets %}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="selectedTicketIds.includes('{{ t.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
<td class="px-4 py-3">
<input type="checkbox" class="ticket-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" :value="'{{ t.id }}'" x-model="selectedTicketIds">
</td>
{% if "uuid" in tickets_columns %}<td class="px-4 py-3 text-xs font-mono text-gray-500 dark:text-gray-400">{{ t.uuid }}</td>{% endif %}
{% if "name" in tickets_columns %}<td class="px-4 py-3 font-medium text-gray-900 dark:text-white max-w-xs truncate">{{ t.name }}</td>{% endif %}
{% if "status" in tickets_columns %}<td class="px-4 py-3"><span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium" style="color:{{ t.status.color }};background-color:{{ t.status.color }}20">{{ t.status.name }}</span></td>{% endif %}
{% if "priority" in tickets_columns %}<td class="px-4 py-3">{% if t.priority %}<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium" style="color:{{ t.priority.color }};background-color:{{ t.priority.color }}20">{{ t.priority.name }}</span>{% else %}<span class="text-gray-400">-</span>{% endif %}</td>{% endif %}
{% if "assignees" in tickets_columns %}<td class="px-4 py-3">{% if t.assignees.all %}<div class="flex -space-x-1.5">{% for a in t.assignees.all|slice:":3" %}<div class="w-6 h-6 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center text-[10px] font-semibold text-blue-600 dark:text-blue-400 border-2 border-white dark:border-gray-700" title="{{ a.get_full_name|default:a.username }}">{{ a.get_full_name|default:a.username|first|upper }}</div>{% endfor %}{% if t.assignees.all|length > 3 %}<div class="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center text-[10px] text-gray-500 border-2 border-white dark:border-gray-700">+{{ t.assignees.all|length|add:"-3" }}</div>{% endif %}</div>{% else %}<span class="text-gray-400">-</span>{% endif %}</td>{% endif %}
{% if "start_date" in tickets_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ t.start_date|date:"d/m/Y"|default:"-" }}</td>{% endif %}
{% if "due_date" in tickets_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ t.due_date|date:"d/m/Y"|default:"-" }}</td>{% endif %}
{% if "created_at" in tickets_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ t.created_at|date:"d/m/Y" }}</td>{% endif %}
<td class="px-4 py-3"><div class="flex items-center gap-1.5"><button type="button" @click="editTicketData = {id: '{{ t.id }}', name: '{{ t.name|escapejs }}', description: '{{ t.description|escapejs }}', status_id: '{{ t.status_id }}', priority_id: '{{ t.priority_id|default:'' }}', start_date: '{{ t.start_date|date:'Y-m-d' }}', due_date: '{{ t.due_date|date:'Y-m-d' }}'}; editTicketOpen = true" 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</button><form method="post" action="{% url 'ticket-delete' t.id %}" class="inline">{% csrf_token %}<button type="button" @click="confirmAction({title:'Delete Ticket', message:'Delete this ticket?', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm">Delete</button></form></div></td>
</tr>
{% empty %}
<tr><td colspan="11" class="px-4 py-8 text-center text-sm text-gray-400">No tickets found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% if tickets_paginator.paginator.num_pages > 1 %}
<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">Showing {{ tickets.start_index }}–{{ tickets.end_index }} of {{ tickets.paginator.count }}</p>
<div class="flex items-center gap-1">
<form method="get" style="display:inline">
{% if tickets_search %}<input type="hidden" name="tickets_search" value="{{ tickets_search }}">{% endif %}
{% if tickets_status %}<input type="hidden" name="tickets_status" value="{{ tickets_status }}">{% endif %}
{% if tickets_priority %}<input type="hidden" name="tickets_priority" value="{{ tickets_priority }}">{% endif %}
<select name="tickets_per_page" onchange="this.form.submit()" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-950">
<option value="10" {% if tickets_per_page == 10 %}selected{% endif %}>10</option>
<option value="20" {% if tickets_per_page == 20 %}selected{% endif %}>20</option>
<option value="50" {% if tickets_per_page == 50 %}selected{% endif %}>50</option>
</select>
</form>
<div class="flex items-center gap-1 ml-2">
{% if tickets.has_previous %}
<a href="?tickets_page=1{% if tickets_search %}&tickets_search={{ tickets_search }}{% endif %}{% if tickets_status %}&tickets_status={{ tickets_status }}{% endif %}{% if tickets_priority %}&tickets_priority={{ tickets_priority }}{% endif %}&tickets_per_page={{ tickets_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&laquo;</a>
<a href="?tickets_page={{ tickets.previous_page_number }}{% if tickets_search %}&tickets_search={{ tickets_search }}{% endif %}{% if tickets_status %}&tickets_status={{ tickets_status }}{% endif %}{% if tickets_priority %}&tickets_priority={{ tickets_priority }}{% endif %}&tickets_per_page={{ tickets_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&lsaquo;</a>
{% endif %}
<span class="px-2 py-1 text-xs text-gray-500">Page {{ tickets.number }}/{{ tickets.paginator.num_pages }}</span>
{% if tickets.has_next %}
<a href="?tickets_page={{ tickets.next_page_number }}{% if tickets_search %}&tickets_search={{ tickets_search }}{% endif %}{% if tickets_status %}&tickets_status={{ tickets_status }}{% endif %}{% if tickets_priority %}&tickets_priority={{ tickets_priority }}{% endif %}&tickets_per_page={{ tickets_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&rsaquo;</a>
<a href="?tickets_page={{ tickets.paginator.num_pages }}{% if tickets_search %}&tickets_search={{ tickets_search }}{% endif %}{% if tickets_status %}&tickets_status={{ tickets_status }}{% endif %}{% if tickets_priority %}&tickets_priority={{ tickets_priority }}{% endif %}&tickets_per_page={{ tickets_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&raquo;</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
<div x-show="activeTab === 'notes'" x-cloak class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-600 flex flex-wrap items-center justify-between gap-4">
<h3 class="text-base font-semibold text-gray-900 dark:text-white">Project Notes</h3>
<div class="flex items-center gap-2">
<form method="get" class="flex items-center gap-2">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-xs"></i>
<input type="text" name="notes_search" value="{{ notes_search }}" placeholder="Search notes..." class="pl-8 pr-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-950 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
</div>
<input type="hidden" name="notes_per_page" value="{{ notes_per_page }}">
{% if notes_search %}<a href="{% url 'project-edit' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
</form>
<button type="button" @click="createNoteData = { title: '', content: '' }; createNoteOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm"><i class="fas fa-plus"></i> New Note</button>
</div>
</div>
<form id="notes-bulk-delete-form" method="post" action="{% url 'notes-bulk-delete' %}">
{% csrf_token %}
<template x-for="id in selectedNoteIds" x-key="id">
<input type="hidden" name="ids" :value="id">
</template>
</form>
<div x-show="selectedNoteIds.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="selectedNoteIds.length"></span> selected
</span>
<button type="button" @click="confirmAction({title:'Delete Notes', message:'Delete ' + selectedNoteIds.length + ' note(s)?', confirmText:'Delete', danger:true, onConfirm:function(){ document.getElementById('notes-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="selectedNoteIds = []; selectAllNotes = 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="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 dark:bg-gray-800">
<tr>
<th class="px-4 py-3 w-10">
<input type="checkbox" @change="selectAllNotes = !selectAllNotes; selectedNoteIds = selectAllNotes ? Array.from(document.querySelectorAll('.note-checkbox')).map(cb => cb.value) : []" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500">
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Title</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Date</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Content</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Created By</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-600">
{% for n in notes %}
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="selectedNoteIds.includes('{{ n.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
<td class="px-4 py-3">
<input type="checkbox" class="note-checkbox rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" :value="'{{ n.id }}'" x-model="selectedNoteIds">
</td>
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ n.title }}</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ n.note_date|date:"d/m/Y" }}</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400 max-w-xs truncate">{{ n.content|striptags|truncatewords:20|default:"-" }}</td>
<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ n.created_by.get_full_name|default:n.created_by.username }}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<button type="button" @click="editNoteData = {id: '{{ n.id }}', title: '{{ n.title|escapejs }}', content: '{{ n.content|escapejs }}'}; editNoteOpen = true" 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 mr-2">Edit</button>
<form method="post" action="{% url 'notes-bulk-delete' %}" class="inline">
{% csrf_token %}
<input type="hidden" name="ids" value="{{ n.id }}">
<button type="button" @click="confirmAction({title:'Delete Note', message:'Delete this note?', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm">Delete</button>
</form>
</td>
</tr>
{% empty %}
<tr><td colspan="6" class="px-4 py-8 text-center text-sm text-gray-400">No notes found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% if notes_paginator.paginator.num_pages > 1 %}
<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">Showing {{ notes.start_index }}–{{ notes.end_index }} of {{ notes.paginator.count }}</p>
<div class="flex items-center gap-1">
<form method="get" style="display:inline">
{% if notes_search %}<input type="hidden" name="notes_search" value="{{ notes_search }}">{% endif %}
<select name="notes_per_page" onchange="this.form.submit()" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2 py-1 bg-white dark:bg-gray-950">
<option value="10" {% if notes_per_page == 10 %}selected{% endif %}>10</option>
<option value="20" {% if notes_per_page == 20 %}selected{% endif %}>20</option>
<option value="50" {% if notes_per_page == 50 %}selected{% endif %}>50</option>
</select>
</form>
<div class="flex items-center gap-1 ml-2">
{% if notes.has_previous %}
<a href="?notes_page=1{% if notes_search %}&notes_search={{ notes_search }}{% endif %}&notes_per_page={{ notes_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&laquo;</a>
<a href="?notes_page={{ notes.previous_page_number }}{% if notes_search %}&notes_search={{ notes_search }}{% endif %}&notes_per_page={{ notes_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&lsaquo;</a>
{% endif %}
<span class="px-2 py-1 text-xs text-gray-500">Page {{ notes.number }}/{{ notes.paginator.num_pages }}</span>
{% if notes.has_next %}
<a href="?notes_page={{ notes.next_page_number }}{% if notes_search %}&notes_search={{ notes_search }}{% endif %}&notes_per_page={{ notes_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&rsaquo;</a>
<a href="?notes_page={{ notes.paginator.num_pages }}{% if notes_search %}&notes_search={{ notes_search }}{% endif %}&notes_per_page={{ notes_per_page }}" class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">&raquo;</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
{% if project %}
{% include "components/external_dashboard_modal.html" with show_state="externalModalOpen" %}
<script>
function copyToClipboard(elementId, message) {
const element = document.getElementById(elementId);
element.select();
element.setSelectionRange(0, 99999);
navigator.clipboard.writeText(element.value).catch(function() {
document.execCommand('copy');
});
showToast(message, 'success');
}
</script>
{% endif %}
{% if project %}
<!-- Create Status Modal -->
<div x-cloak x-show="createStatusOpen" class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto">
<div class="fixed inset-0 bg-black/50" @click="createStatusOpen = false"></div>
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-4xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8">
<div class="px-8 py-5 flex items-center justify-between">
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Create Status</h3>
<button @click="createStatusOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<form method="post" action="{% url 'ticket-status-create' %}">
{% csrf_token %}
<input type="hidden" name="project_id" value="{{ project.id }}">
<div class="px-8 pb-6 space-y-5">
<div class="grid grid-cols-2 gap-6">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Status Name<span class="text-red-500">*</span></label>
<input type="text" name="name" x-model="createStatusData.name" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Sort Order<span class="text-red-500">*</span></label>
<input type="number" name="sort_order" x-model="createStatusData.sort_order" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div class="grid grid-cols-2 gap-6">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Color (Hex)<span class="text-red-500">*</span></label>
<div class="flex items-center gap-2">
<toolcool-color-picker popup-position="left" :color="createStatusData.color" @change="createStatusData.color = $event.detail.hex" class="shrink-0"></toolcool-color-picker>
<input type="text" name="color" x-model="createStatusData.color" required
class="w-full rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none uppercase font-mono">
</div>
</div>
<div class="flex items-center pt-7">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="is_completed" x-model="createStatusData.is_completed" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
<span class="text-sm font-semibold text-gray-900 dark:text-gray-300">Marks ticket as completed</span>
</label>
</div>
</div>
</div>
<div class="px-8 py-5 flex justify-start gap-3">
<button type="submit" class="px-5 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors shadow-sm">Create status</button>
<button type="button" @click="createStatusOpen = false" class="px-5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Add Member Modal -->
<div x-cloak x-show="createMemberOpen" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/50" @click="createMemberOpen = false"></div>
<div class="relative bg-white dark:bg-gray-900 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 w-full max-w-md mx-4 sm:mx-auto my-auto z-10 overflow-visible">
<!-- Header -->
<div class="px-6 py-5 flex items-center justify-between">
<h3 class="text-base font-bold text-gray-900 dark:text-white">Attach User</h3>
<button type="button" @click="createMemberOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
</div>
<!-- Body & Form -->
<form method="post" action="{% url 'project-member-create' %}">
{% csrf_token %}
<input type="hidden" name="project_id" value="{{ project.id }}">
<div class="px-6 pb-4">
<div class="flex shadow-sm rounded-lg relative" x-data="{
open: false,
search: '',
options: [
{% for u in users %}
{% if u.id not in member_ids %}
{ id: {{ u.id }}, name: '{{ u.get_full_name|default:u.username|escapejs }} (@{{ u.username|escapejs }})' },
{% endif %}
{% endfor %}
],
get filteredOptions() {
if (this.search === '') return this.options;
return this.options.filter(i => i.name.toLowerCase().includes(this.search.toLowerCase()));
}
}">
<input type="hidden" name="user_id" x-model="createMemberData.user_id" required>
<button type="button" @click="open = !open" @click.away="open = false" class="flex-1 flex items-center justify-between px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-l-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none hover:bg-gray-50 dark:hover:bg-gray-700">
<span x-text="createMemberData.user_id ? options.find(o => o.id == createMemberData.user_id)?.name : 'Select an option'" :class="!createMemberData.user_id && 'text-gray-500'"></span>
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
</button>
<!-- Dropdown Menu -->
<div x-cloak x-show="open" class="absolute left-0 right-11 top-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50 overflow-hidden">
<div class="p-2 border-b border-gray-100 dark:border-gray-700">
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-xs"></i>
<input type="text" x-model="search" placeholder="Search users..." class="w-full pl-8 pr-3 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-1 focus:ring-blue-500 text-gray-900 dark:text-white" @click.stop>
</div>
</div>
<ul class="max-h-48 overflow-y-auto py-1">
<template x-for="option in filteredOptions" :key="option.id">
<li @click="createMemberData.user_id = option.id; open = false; search = ''" class="px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-blue-50 dark:hover:bg-blue-900/30 cursor-pointer" x-text="option.name"></li>
</template>
<li x-show="filteredOptions.length === 0" class="px-3 py-4 text-sm text-gray-500 text-center">No users found</li>
</ul>
</div>
<!-- Plus Button to Open Create User Modal -->
<button type="button" @click="createMemberOpen = false; setTimeout(() => { createUserOpen = true }, 100)" class="px-4 text-gray-500 border border-l-0 border-gray-200 dark:border-gray-600 rounded-r-lg bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center justify-center transition-colors">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<!-- Footer -->
<div class="px-6 pb-6 pt-2 flex flex-wrap gap-2.5">
<button type="submit" class="px-4 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">Attach</button>
<button type="submit" name="create_another" value="1" class="px-4 py-1.5 text-sm font-medium text-gray-900 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">Attach & attach another</button>
<button type="button" @click="createMemberOpen = false" class="px-4 py-1.5 text-sm font-medium text-gray-900 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Create User Modal -->
<div x-cloak x-show="createUserOpen" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/50" @click="createUserOpen = false"></div>
<div class="relative bg-white dark:bg-gray-900 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 w-full max-w-md mx-4 sm:mx-auto my-auto z-10 max-h-[90vh] overflow-y-auto">
<!-- Header -->
<div class="px-6 py-5 flex items-center justify-between">
<h3 class="text-base font-bold text-gray-900 dark:text-white">Create</h3>
<button type="button" @click="createUserOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
</div>
<!-- Body & Form -->
<form method="post" action="{% url 'user-create' %}?next={{ request.path }}">
{% csrf_token %}
<div class="px-6 pb-4 space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Name<span class="text-red-500">*</span></label>
<input type="text" name="name" required class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Email<span class="text-red-500">*</span></label>
<input type="email" name="email" required class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Email verified at</label>
<input type="datetime-local" name="email_verified_at" class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-400 dark:text-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Password<span class="text-red-500">*</span></label>
<input type="password" name="password" required class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1">Roles</label>
<select name="role" class="w-full px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500">
<option value="" disabled selected>Select an option</option>
<option value="Super Admin">Super Admin</option>
<option value="Admin User">Admin User</option>
<option value="Member User">Member User</option>
</select>
</div>
</div>
<!-- Footer -->
<div class="px-6 pb-6 pt-2 flex flex-wrap gap-2.5">
<button type="submit" class="px-4 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">Create</button>
<button type="button" @click="createUserOpen = false" class="px-4 py-1.5 text-sm font-medium text-gray-900 dark:text-gray-200 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Edit Status Modal -->
<div x-cloak x-show="editStatusOpen" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/50" @click="editStatusOpen = false"></div>
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-4xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8">
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-600 flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Edit Status</h3>
<button @click="editStatusOpen = false" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
</div>
<form method="post" :action="'/ticket-statuses/' + editStatusData.id + '/edit/'">
{% csrf_token %}
<div class="px-6 py-5 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Name</label>
<input type="text" name="name" x-model="editStatusData.name" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Color (Hex)</label>
<div class="flex items-center gap-2">
<toolcool-color-picker popup-position="left" :color="editStatusData.color" @change="editStatusData.color = $event.detail.hex" class="shrink-0"></toolcool-color-picker>
<input type="text" name="color" x-model="editStatusData.color" required
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none uppercase font-mono">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Sort Order</label>
<input type="number" name="sort_order" x-model="editStatusData.sort_order" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="flex items-center gap-2">
<input type="checkbox" name="is_completed" x-model="editStatusData.is_completed" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Marks ticket as completed</label>
</div>
</div>
<div class="px-6 py-4 border-t border-gray-100 dark:border-gray-600 bg-gray-50 dark:bg-gray-800/30 flex justify-end gap-3">
<button type="button" @click="editStatusOpen = false" class="px-4 py-2 text-sm 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">Cancel</button>
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-lg hover:bg-blue-700 transition-colors shadow-sm">Save Changes</button>
</div>
</form>
</div>
</div>
<!-- Create Note Modal -->
<div x-cloak x-show="createNoteOpen" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/50" @click="createNoteOpen = false"></div>
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-4xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8"
>
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-600 flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Create Note</h3>
<button @click="createNoteOpen = false" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
</div>
<form method="post" action="{% url 'project-note-create' %}" >
{% csrf_token %}
<input type="hidden" name="project_id" value="{{ project.id }}">
<div class="px-6 py-5 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Title</label>
<input type="text" name="title" x-model="createNoteData.title" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="prose-container">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Content</label>
{% include "components/rich_text_editor.html" with input_name="content" initial_value="" %}
</div>
</div>
<div class="px-8 py-5 flex justify-end gap-3">
<button type="button" @click="createNoteOpen = false" class="px-4 py-2 text-sm 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">Cancel</button>
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-lg hover:bg-blue-700 transition-colors shadow-sm">Save Note</button>
</div>
</form>
</div>
</div>
<!-- Edit Note Modal -->
<div x-cloak x-show="editNoteOpen" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/50" @click="editNoteOpen = false"></div>
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-4xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8"
>
<div class="px-8 py-5 flex items-center justify-between">
<h3 class="text-xl font-bold text-gray-900 dark:text-white" x-text="'Edit ' + editNoteData.title">Edit Note</h3>
<button @click="editNoteOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<form method="post" :action="'/project-notes/' + editNoteData.id + '/edit/'" >
{% csrf_token %}
<div class="px-8 pb-6 space-y-5">
<div class="grid grid-cols-1 gap-6">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Note Title<span class="text-red-500">*</span></label>
<input type="text" name="title" x-model="editNoteData.title" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Content</label>
{% include "components/rich_text_editor.html" with input_name="content" alpine_model="editNoteData.content" %}
</div>
</div>
</div>
<div class="px-8 py-5 flex justify-start gap-3">
<button type="submit" class="px-5 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors shadow-sm">Save changes</button>
<button type="button" @click="editNoteOpen = false" class="px-5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Create Epic Modal -->
<div x-cloak x-show="createEpicOpen" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/50" @click="createEpicOpen = false"></div>
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-4xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8"
>
<div class="px-8 py-5 flex items-center justify-between">
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Create Epic</h3>
<button @click="createEpicOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<form method="post" action="{% url 'epic-create-project' project.id %}" >
{% csrf_token %}
<div class="px-8 pb-6 space-y-5">
<div class="grid grid-cols-2 gap-6">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Epic Name<span class="text-red-500">*</span></label>
<input type="text" name="name" x-model="createEpicData.name" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Sort Order</label>
<input type="number" name="sort_order" x-model="createEpicData.sort_order" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
<span class="text-xs text-gray-500 dark:text-gray-400 mt-1 block">Lower numbers appear first</span>
</div>
</div>
<div class="grid grid-cols-2 gap-6">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Start Date</label>
<input type="date" name="start_date" x-model="createEpicData.start_date" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">End Date</label>
<input type="date" name="end_date" x-model="createEpicData.end_date" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Description</label>
{% include "components/rich_text_editor.html" with input_name="description" alpine_model="createEpicData.description" %}
</div>
</div>
<div class="px-8 py-5 flex justify-start gap-3">
<button type="submit" class="px-5 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors shadow-sm">Create Epic</button>
<button type="button" @click="createEpicOpen = false" class="px-5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Edit Epic Modal -->
{% include "components/edit_epic_modal.html" %}
<!-- Create Ticket Modal -->
{% include "components/create_ticket_modal.html" %}
<!-- Edit Ticket Modal -->
<div x-cloak x-show="editTicketOpen" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/50" @click="editTicketOpen = false"></div>
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-4xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8"
>
<div class="px-8 py-5 flex items-center justify-between">
<h3 class="text-xl font-bold text-gray-900 dark:text-white" x-text="'Edit ' + editTicketData.name">Edit Ticket</h3>
<button @click="editTicketOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<form method="post" :action="'/tickets/' + editTicketData.id + '/edit/'" >
{% csrf_token %}
<div class="px-8 pb-6 space-y-5">
<div class="grid grid-cols-2 gap-6">
<div class="col-span-2">
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Ticket Name<span class="text-red-500">*</span></label>
<input type="text" name="name" x-model="editTicketData.name" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div class="grid grid-cols-2 gap-6">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Status</label>
<select name="status_id" x-model="editTicketData.status_id" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
{% for s in statuses %}
<option value="{{ s.id }}">{{ s.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Priority</label>
<select name="priority_id" x-model="editTicketData.priority_id" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">— None —</option>
{% for p in priorities %}
<option value="{{ p.id }}">{{ p.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="grid grid-cols-2 gap-6">
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Start Date</label>
<input type="date" name="start_date" x-model="editTicketData.start_date" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Due Date</label>
<input type="date" name="due_date" x-model="editTicketData.due_date" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-900 dark:text-gray-300 mb-1.5">Description</label>
{% include "components/rich_text_editor.html" with input_name="description" alpine_model="editTicketData.description" %}
</div>
</div>
<div class="px-8 py-5 flex justify-start gap-3">
<button type="submit" class="px-5 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors shadow-sm">Save changes</button>
<button type="button" @click="editTicketOpen = false" class="px-5 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors shadow-sm">Cancel</button>
</div>
</form>
</div>
</div>
</div>
{% endif %}
{% endif %}
{% endblock %}
{% block extra_scripts %}
{% endblock %}