Enable management of automation rules with UI buttons, modal, and status condition handling
844 lines
65 KiB
HTML
844 lines
65 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}{{ project.name }}{% 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><span class="text-gray-300 dark:text-gray-600">/</span></li>
|
||
<li><a href="{% url 'projects-list' %}" class="hover:text-gray-700 dark:hover:text-gray-300">Projects</a></li>
|
||
<li><span class="text-gray-300 dark:text-gray-600">/</span></li>
|
||
<li class="text-gray-900 dark:text-white font-medium" aria-current="page">{{ project.name }}</li>
|
||
</ol>
|
||
</nav>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
|
||
<div class="mb-6 flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<span class="inline-flex items-center px-2.5 py-0.5 rounded text-xs font-semibold text-white" style="background-color:{{ project.color|default:'#3B82F6' }}">{{ project.ticket_prefix }}</span>
|
||
<div>
|
||
<h1 class="text-2xl font-bold">{{ project.name }}</h1>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<a href="{% url 'projects-list' %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-arrow-left mr-1"></i> Back</a>
|
||
<a href="{% url 'project-board-project' project.id %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-columns"></i> Board</a>
|
||
<button @click="window.location.reload()" class="dk-btn dk-btn-ghost text-sm text-gray-700 dark:text-gray-300">
|
||
<i class="fas fa-sync"></i> Refresh
|
||
</button>
|
||
<button @click="$store.projectDetail.externalModalOpen = true" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-globe"></i> External</button>
|
||
{% if is_admin %}
|
||
<button @click="$store.projectDetail.permissionsOpen = true" class="dk-btn dk-btn-ghost text-sm text-orange-600">
|
||
<i class="fas fa-shield-halved"></i> Permissions
|
||
</button>
|
||
<button @click="$store.projectDetail.automationOpen = true" class="dk-btn dk-btn-ghost text-sm text-cyan-600">
|
||
<i class="fas fa-robot"></i> Automation
|
||
</button>
|
||
<button @click="$store.projectDetail.manageStatusesOpen = true" class="dk-btn dk-btn-ghost text-sm text-purple-600">
|
||
<i class="fas fa-cog"></i> Statuses
|
||
</button>
|
||
{% endif %}
|
||
<a href="{% url 'project-edit' project.id %}" class="dk-btn dk-btn-primary text-sm"><i class="fas fa-pen"></i> Edit</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="max-w-full space-y-6">
|
||
|
||
{# Row 1: Left col (Project Info + Timestamps) | Right col (Statistics) #}
|
||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
||
|
||
{# LEFT: Project Information + Timestamps stacked #}
|
||
<div class="space-y-6">
|
||
|
||
{# Project Information Card #}
|
||
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 p-6 space-y-6">
|
||
<h3 class="text-base font-bold text-gray-900 dark:text-white border-b border-gray-100 dark:border-gray-800 pb-4">Project Information</h3>
|
||
|
||
<div class="grid grid-cols-2 gap-6">
|
||
<!-- Project Name -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Project Name</h4>
|
||
<p class="text-lg font-bold text-gray-900 dark:text-white">{{ project.name }}</p>
|
||
</div>
|
||
|
||
<!-- Ticket Prefix -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Ticket Prefix</h4>
|
||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400">
|
||
{{ project.ticket_prefix }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Description -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Description</h4>
|
||
{% if project.description %}
|
||
<div class="text-sm text-gray-800 dark:text-gray-300 leading-relaxed prose prose-sm dark:prose-invert max-w-none">{{ project.description|safe }}</div>
|
||
{% else %}
|
||
<p class="text-sm text-gray-400 dark:text-gray-600 italic">No description provided.</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-6">
|
||
<!-- Start Date -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Start Date</h4>
|
||
<p class="text-sm text-gray-800 dark:text-gray-300">{{ project.start_date|date:"d/m/Y"|default:"—" }}</p>
|
||
</div>
|
||
|
||
<!-- End Date -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">End Date</h4>
|
||
<p class="text-sm text-gray-800 dark:text-gray-300">{{ project.end_date|date:"d/m/Y"|default:"—" }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-6">
|
||
<!-- Remaining Days -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Remaining Days</h4>
|
||
{% if remaining_days is not None %}
|
||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold {% if remaining_days == 1 %}bg-red-50 text-red-600 dark:bg-red-900/30 dark:text-red-400{% elif remaining_days <= 7 %}bg-amber-50 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400{% else %}bg-green-50 text-green-600 dark:bg-green-900/30 dark:text-green-400{% endif %}">
|
||
{{ remaining_days }} days
|
||
</span>
|
||
{% else %}
|
||
<span class="text-sm text-gray-400">—</span>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Pinned Status -->
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Pinned Status</h4>
|
||
{% if project.pinned_date %}
|
||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-amber-50 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400">Pinned</span>
|
||
{% else %}
|
||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400">Not pinned</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Timestamps Card #}
|
||
<div x-data="{ open: true }" class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10">
|
||
<button @click="open = !open" class="w-full px-6 py-4 flex items-center justify-between border-b border-gray-100 dark:border-gray-800 text-left focus:outline-none">
|
||
<h3 class="text-base font-bold text-gray-900 dark:text-white">Timestamps</h3>
|
||
<i class="fas fa-chevron-up text-gray-400 transition-transform duration-200" :class="{'rotate-180': !open}"></i>
|
||
</button>
|
||
<div x-show="open" x-collapse class="px-6 py-6">
|
||
<div class="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Created At</h4>
|
||
<p class="text-sm text-gray-800 dark:text-gray-300">{{ project.created_at|date:"d/m/Y H:i" }}</p>
|
||
</div>
|
||
<div>
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-1.5">Last Updated</h4>
|
||
<p class="text-sm text-gray-800 dark:text-gray-300">{{ project.updated_at|date:"d/m/Y H:i" }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{# RIGHT: Project Statistics Card #}
|
||
<div class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 h-fit">
|
||
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-800">
|
||
<h3 class="text-base font-bold text-gray-900 dark:text-white">Project Statistics</h3>
|
||
</div>
|
||
|
||
<div class="px-6 py-5 flex items-start justify-between gap-8 overflow-x-auto whitespace-nowrap">
|
||
<div class="flex flex-col gap-2">
|
||
<span class="text-sm font-semibold text-gray-900 dark:text-white">Total Members</span>
|
||
<div>
|
||
<span class="inline-flex items-center justify-center px-2 py-0.5 rounded text-xs font-bold bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400">{{ members_count }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex flex-col gap-2">
|
||
<span class="text-sm font-semibold text-gray-900 dark:text-white">Total Tickets</span>
|
||
<div>
|
||
<span class="inline-flex items-center justify-center px-2 py-0.5 rounded text-xs font-bold bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400">{{ tickets_count_total }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex flex-col gap-2">
|
||
<span class="text-sm font-semibold text-gray-900 dark:text-white">Total Epics</span>
|
||
<div>
|
||
<span class="inline-flex items-center justify-center px-2 py-0.5 rounded text-xs font-bold bg-amber-50 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400">{{ epics_count }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex flex-col gap-2">
|
||
<span class="text-sm font-semibold text-gray-900 dark:text-white">Ticket Statuses</span>
|
||
<div>
|
||
<span class="inline-flex items-center justify-center px-2 py-0.5 rounded text-xs font-bold bg-green-50 text-green-600 dark:bg-green-900/30 dark:text-green-400">{{ statuses_count }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% include "components/project_tabs.html" with active_tab_var="$store.projectDetail.activeTab" %}
|
||
|
||
<!-- STATUSES TAB -->
|
||
<div x-show="$store.projectDetail.activeTab === 'statuses'" 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-700 flex flex-wrap items-center justify-between gap-4">
|
||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">Ticket Statuses</h3>
|
||
<div class="flex items-center gap-3 flex-wrap">
|
||
{% if is_admin %}
|
||
<button type="button" @click="$store.projectDetail.permissionsOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-orange-600 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg hover:bg-orange-100 dark:hover:bg-orange-900/30 transition-colors">
|
||
<i class="fas fa-shield-halved"></i> Permissions
|
||
</button>
|
||
<button type="button" @click="$store.projectDetail.manageStatusesOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-purple-600 bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800 rounded-lg hover:bg-purple-100 dark:hover:bg-purple-900/30 transition-colors">
|
||
<i class="fas fa-cog"></i> Manage
|
||
</button>
|
||
{% endif %}
|
||
<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="statuses_search" value="{{ statuses_search }}" placeholder="Search..." 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-900 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
|
||
</div>
|
||
{% if statuses_search %}<a href="{% url 'project-detail' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
|
||
</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-900 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-900 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg z-20 py-1">
|
||
<form method="get">
|
||
{% for col in all_statuses_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="statuses_columns" value="{{ col }}" {% if col in statuses_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>
|
||
</div>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
||
<tr>
|
||
{% if "name" in statuses_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>{% endif %}
|
||
{% if "color" in statuses_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Color</th>{% endif %}
|
||
{% if "sort_order" in statuses_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Order</th>{% endif %}
|
||
{% if "is_completed" in statuses_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Completed</th>{% endif %}
|
||
{% if is_admin %}<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||
{% for s in statuses %}
|
||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||
{% if "name" in statuses_columns %}<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ s.name }}</td>{% endif %}
|
||
{% if "color" in statuses_columns %}<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 flex-shrink-0" style="background-color:{{ s.color }}"></span>{{ s.color }}</span></td>{% endif %}
|
||
{% if "sort_order" in statuses_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ s.sort_order }}</td>{% endif %}
|
||
{% if "is_completed" in statuses_columns %}<td class="px-4 py-3">{% if s.is_completed %}<span class="px-2 py-0.5 rounded-full text-xs bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400">Yes</span>{% else %}<span class="text-gray-400 text-xs">No</span>{% endif %}</td>{% endif %}
|
||
{% if is_admin %}
|
||
<td class="px-4 py-3 text-right whitespace-nowrap">
|
||
<a href="{% url 'project-edit' project.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 mr-1">
|
||
<i class="fas fa-pen"></i>
|
||
</a>
|
||
<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">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</form>
|
||
</td>
|
||
{% endif %}
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="{% if is_admin %}5{% else %}4{% endif %}" class="px-4 py-8 text-center text-sm text-gray-400">No statuses found.</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% if statuses_paginator.paginator.num_pages > 1 %}
|
||
<div class="px-6 py-3 border-t border-gray-100 dark:border-gray-700 flex items-center justify-between text-xs text-gray-500">
|
||
<span>{{ statuses.start_index }}–{{ statuses.end_index }} of {{ statuses.paginator.count }}</span>
|
||
<div class="flex items-center gap-1">
|
||
{% if statuses.has_previous %}<a href="?statuses_page={{ statuses.previous_page_number }}&statuses_search={{ statuses_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">‹</a>{% endif %}
|
||
<span class="px-2">{{ statuses.number }}/{{ statuses.paginator.num_pages }}</span>
|
||
{% if statuses.has_next %}<a href="?statuses_page={{ statuses.next_page_number }}&statuses_search={{ statuses_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">›</a>{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- MEMBERS TAB -->
|
||
<div x-show="$store.projectDetail.activeTab === 'members'" 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-700 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-3 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="members_search" value="{{ members_search }}" placeholder="Search..." 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-900 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
|
||
</div>
|
||
{% if members_search %}<a href="{% url 'project-detail' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
|
||
</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-900 hover:bg-gray-50 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-900 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg z-20 py-1">
|
||
<form method="get">
|
||
{% for col in all_members_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="members_columns" value="{{ col }}" {% if col in members_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>
|
||
</div>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
||
<tr>
|
||
{% if "name" in members_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>{% endif %}
|
||
{% if "email" in members_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>{% endif %}
|
||
{% if "joined" in members_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Joined</th>{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||
{% for pm in members %}
|
||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||
{% if "name" in members_columns %}
|
||
<td class="px-4 py-3">
|
||
<div class="flex items-center gap-3">
|
||
<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 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>
|
||
{% endif %}
|
||
{% if "email" in members_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ pm.user.email }}</td>{% endif %}
|
||
{% if "joined" in members_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ pm.joined_at|date:"d/m/Y"|default:"-" }}</td>{% endif %}
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="3" class="px-4 py-8 text-center text-sm text-gray-400">No members found.</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% if members_paginator.paginator.num_pages > 1 %}
|
||
<div class="px-6 py-3 border-t border-gray-100 dark:border-gray-700 flex items-center justify-between text-xs text-gray-500">
|
||
<span>{{ members.start_index }}–{{ members.end_index }} of {{ members.paginator.count }}</span>
|
||
<div class="flex items-center gap-1">
|
||
{% if members.has_previous %}<a href="?members_page={{ members.previous_page_number }}&members_search={{ members_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">‹</a>{% endif %}
|
||
<span class="px-2">{{ members.number }}/{{ members.paginator.num_pages }}</span>
|
||
{% if members.has_next %}<a href="?members_page={{ members.next_page_number }}&members_search={{ members_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">›</a>{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- EPICS TAB -->
|
||
<form id="epics-bulk-delete-form" method="post" action="{% url 'epics-bulk-delete' %}">{% csrf_token %}<template x-for="id in $store.projectDetail.epicsSelectedIds" x-key="id"><input type="hidden" name="ids" :value="id"></template></form>
|
||
<div x-show="$store.projectDetail.activeTab === 'epics'" class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
|
||
<div x-show="$store.projectDetail.epicsSelectedIds.length > 0" x-cloak class="px-6 py-2 border-b border-gray-100 dark:border-gray-700 flex items-center gap-3 bg-blue-50 dark:bg-blue-900/20">
|
||
<span class="text-xs font-medium text-blue-700 dark:text-blue-300"><span x-text="$store.projectDetail.epicsSelectedIds.length"></span> selected</span>
|
||
<button type="button" @click="confirmAction({title:'Delete Epics',message:'Delete '+$store.projectDetail.epicsSelectedIds.length+' epic(s)?',confirmText:'Delete',danger:true,onConfirm:function(){ document.getElementById('epics-bulk-delete-form').submit() }})" class="text-xs text-red-600 hover:text-red-700 font-medium"><i class="fas fa-trash mr-1"></i>Delete</button>
|
||
<button type="button" @click="$store.projectDetail.epicsSelectedIds = []" class="text-xs text-gray-500 hover:text-gray-700 ml-auto"><i class="fas fa-times mr-1"></i>Clear</button>
|
||
</div>
|
||
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 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-3 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="epics_search" value="{{ epics_search }}" placeholder="Search..." 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-900 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
|
||
</div>
|
||
{% if epics_search %}<a href="{% url 'project-detail' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
|
||
</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-900 hover:bg-gray-50 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-900 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg z-20 py-1">
|
||
<form method="get">
|
||
{% for col in all_epics_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="epics_columns" value="{{ col }}" {% if col in epics_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>
|
||
</div>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
||
<tr>
|
||
<th class="px-3 py-3 w-10"><input type="checkbox" @change="const cbs=document.querySelectorAll('.epic-row-cb');$store.projectDetail.epicsSelectedIds=$event.target.checked?Array.from(cbs).map(c=>c.value):[]" class="rounded border-gray-300 dark:border-gray-600 text-blue-600"></th>
|
||
{% if "name" in epics_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>{% endif %}
|
||
{% if "sort_order" in epics_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Order</th>{% endif %}
|
||
{% if "start_date" in epics_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Start</th>{% endif %}
|
||
{% if "end_date" in epics_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">End</th>{% endif %}
|
||
{% if "tickets" in epics_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tickets</th>{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||
{% for e in epics %}
|
||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="$store.projectDetail.epicsSelectedIds.includes('{{ e.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
|
||
<td class="px-3 py-3"><input type="checkbox" class="epic-row-cb rounded border-gray-300 dark:border-gray-600 text-blue-600" value="{{ e.id }}" x-model="$store.projectDetail.epicsSelectedIds"></td>
|
||
{% if "name" in epics_columns %}<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ e.name }}</td>{% endif %}
|
||
{% if "sort_order" in epics_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ e.sort_order }}</td>{% endif %}
|
||
{% if "start_date" in epics_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ e.start_date|date:"d/m/Y"|default:"-" }}</td>{% endif %}
|
||
{% if "end_date" in epics_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ e.end_date|date:"d/m/Y"|default:"-" }}</td>{% endif %}
|
||
{% if "tickets" in epics_columns %}<td class="px-4 py-3"><span class="px-2 py-0.5 rounded-full text-xs bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400">{{ e.ticket_count }}</span></td>{% endif %}
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="6" 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-700 flex items-center justify-between text-xs text-gray-500">
|
||
<span>{{ epics.start_index }}–{{ epics.end_index }} of {{ epics.paginator.count }}</span>
|
||
<div class="flex items-center gap-1">
|
||
{% if epics.has_previous %}<a href="?epics_page={{ epics.previous_page_number }}&epics_search={{ epics_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">‹</a>{% endif %}
|
||
<span class="px-2">{{ epics.number }}/{{ epics.paginator.num_pages }}</span>
|
||
{% if epics.has_next %}<a href="?epics_page={{ epics.next_page_number }}&epics_search={{ epics_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">›</a>{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- TICKETS TAB -->
|
||
<form id="tickets-bulk-delete-form" method="post" action="{% url 'tickets-bulk-delete' %}">{% csrf_token %}<template x-for="id in $store.projectDetail.ticketsSelectedIds" x-key="id"><input type="hidden" name="ids" :value="id"></template></form>
|
||
<div x-show="$store.projectDetail.activeTab === 'tickets'" class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
|
||
<div x-show="$store.projectDetail.ticketsSelectedIds.length > 0" x-cloak class="px-6 py-2 border-b border-gray-100 dark:border-gray-700 flex items-center gap-3 bg-blue-50 dark:bg-blue-900/20">
|
||
<span class="text-xs font-medium text-blue-700 dark:text-blue-300"><span x-text="$store.projectDetail.ticketsSelectedIds.length"></span> selected</span>
|
||
<button type="button" @click="confirmAction({title:'Delete Tickets',message:'Delete '+$store.projectDetail.ticketsSelectedIds.length+' ticket(s)?',confirmText:'Delete',danger:true,onConfirm:function(){ document.getElementById('tickets-bulk-delete-form').submit() }})" class="text-xs text-red-600 hover:text-red-700 font-medium"><i class="fas fa-trash mr-1"></i>Delete</button>
|
||
<button type="button" @click="$store.projectDetail.ticketsSelectedIds = []" class="text-xs text-gray-500 hover:text-gray-700 ml-auto"><i class="fas fa-times mr-1"></i>Clear</button>
|
||
</div>
|
||
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 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-3 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..." 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-900 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-detail' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
|
||
</form>
|
||
<select name="tickets_status" onchange="window.location.href='?tickets_status='+this.value" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2 py-1.5 bg-white dark:bg-gray-900">
|
||
<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>
|
||
<select name="tickets_priority" onchange="window.location.href='?tickets_priority='+this.value" class="text-xs border border-gray-200 dark:border-gray-600 rounded px-2 py-1.5 bg-white dark:bg-gray-900">
|
||
<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>
|
||
<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-900 hover:bg-gray-50 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-900 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>
|
||
</div>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
||
<tr>
|
||
<th class="px-3 py-3 w-10"><input type="checkbox" @change="const cbs=document.querySelectorAll('.ticket-row-cb');$store.projectDetail.ticketsSelectedIds=$event.target.checked?Array.from(cbs).map(c=>c.value):[]" class="rounded border-gray-300 dark:border-gray-600 text-blue-600"></th>
|
||
{% if "uuid" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>{% endif %}
|
||
{% if "name" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 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 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 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 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 uppercase tracking-wider">Start</th>{% endif %}
|
||
{% if "due_date" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Due</th>{% endif %}
|
||
{% if "created_at" in tickets_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created</th>{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||
{% for t in tickets %}
|
||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="$store.projectDetail.ticketsSelectedIds.includes('{{ t.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
|
||
<td class="px-3 py-3"><input type="checkbox" class="ticket-row-cb rounded border-gray-300 dark:border-gray-600 text-blue-600" value="{{ t.id }}" x-model="$store.projectDetail.ticketsSelectedIds"></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="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="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 border-2 border-white dark:border-gray-900" title="{{ a.get_full_name|default:a.username }}">{{ a.get_full_name|default:a.username|first|upper }}</div>{% endfor %}</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 %}
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="10" 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-700 flex items-center justify-between text-xs text-gray-500">
|
||
<span>{{ tickets.start_index }}–{{ tickets.end_index }} of {{ tickets.paginator.count }}</span>
|
||
<div class="flex items-center gap-1">
|
||
{% if tickets.has_previous %}<a href="?tickets_page={{ tickets.previous_page_number }}&tickets_search={{ tickets_search }}&tickets_status={{ tickets_status }}&tickets_priority={{ tickets_priority }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">‹</a>{% endif %}
|
||
<span class="px-2">{{ tickets.number }}/{{ tickets.paginator.num_pages }}</span>
|
||
{% if tickets.has_next %}<a href="?tickets_page={{ tickets.next_page_number }}&tickets_search={{ tickets_search }}&tickets_status={{ tickets_status }}&tickets_priority={{ tickets_priority }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">›</a>{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- NOTES TAB -->
|
||
<form id="notes-bulk-delete-form" method="post" action="{% url 'notes-bulk-delete' %}">{% csrf_token %}<template x-for="id in $store.projectDetail.notesSelectedIds" x-key="id"><input type="hidden" name="ids" :value="id"></template></form>
|
||
<div x-show="$store.projectDetail.activeTab === 'notes'" class="rounded-xl bg-white dark:bg-gray-950 shadow-sm ring-1 ring-gray-950/5 dark:ring-white/10 overflow-hidden">
|
||
<div x-show="$store.projectDetail.notesSelectedIds.length > 0" x-cloak class="px-6 py-2 border-b border-gray-100 dark:border-gray-700 flex items-center gap-3 bg-blue-50 dark:bg-blue-900/20">
|
||
<span class="text-xs font-medium text-blue-700 dark:text-blue-300"><span x-text="$store.projectDetail.notesSelectedIds.length"></span> selected</span>
|
||
<button type="button" @click="confirmAction({title:'Delete Notes',message:'Delete '+$store.projectDetail.notesSelectedIds.length+' note(s)?',confirmText:'Delete',danger:true,onConfirm:function(){ document.getElementById('notes-bulk-delete-form').submit() }})" class="text-xs text-red-600 hover:text-red-700 font-medium"><i class="fas fa-trash mr-1"></i>Delete</button>
|
||
<button type="button" @click="$store.projectDetail.notesSelectedIds = []" class="text-xs text-gray-500 hover:text-gray-700 ml-auto"><i class="fas fa-times mr-1"></i>Clear</button>
|
||
</div>
|
||
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 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-3 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="notes_search" value="{{ notes_search }}" placeholder="Search..." 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-900 focus:outline-none focus:ring-2 focus:ring-blue-500 w-44">
|
||
</div>
|
||
{% if notes_search %}<a href="{% url 'project-detail' project.id %}" class="text-xs text-gray-500 hover:text-gray-700">Clear</a>{% endif %}
|
||
</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-900 hover:bg-gray-50 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-900 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg z-20 py-1">
|
||
<form method="get">
|
||
{% for col in all_notes_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="notes_columns" value="{{ col }}" {% if col in notes_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>
|
||
</div>
|
||
</div>
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-gray-50 dark:bg-gray-800/50">
|
||
<tr>
|
||
<th class="px-3 py-3 w-10"><input type="checkbox" @change="const cbs=document.querySelectorAll('.note-row-cb');$store.projectDetail.notesSelectedIds=$event.target.checked?Array.from(cbs).map(c=>c.value):[]" class="rounded border-gray-300 dark:border-gray-600 text-blue-600"></th>
|
||
{% if "title" in notes_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Title</th>{% endif %}
|
||
{% if "date" in notes_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>{% endif %}
|
||
{% if "content" in notes_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Content</th>{% endif %}
|
||
{% if "created_by" in notes_columns %}<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created By</th>{% endif %}
|
||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||
{% for n in notes %}
|
||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" :class="$store.projectDetail.notesSelectedIds.includes('{{ n.id }}') && 'bg-blue-50 dark:bg-blue-900/10'">
|
||
<td class="px-3 py-3"><input type="checkbox" class="note-row-cb rounded border-gray-300 dark:border-gray-600 text-blue-600" value="{{ n.id }}" x-model="$store.projectDetail.notesSelectedIds"></td>
|
||
{% if "title" in notes_columns %}<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ n.title }}</td>{% endif %}
|
||
{% if "date" in notes_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400">{{ n.note_date|date:"d/m/Y" }}</td>{% endif %}
|
||
{% if "content" in notes_columns %}<td class="px-4 py-3 text-gray-500 dark:text-gray-400 max-w-xs truncate">{{ n.content|striptags|truncatewords:20|default:"-" }}</td>{% endif %}
|
||
{% if "created_by" in notes_columns %}<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>{% endif %}
|
||
<td class="px-4 py-3">
|
||
<button type="button" @click="$store.projectDetail.viewNoteData = {id: '{{ n.id }}', title: '{{ n.title|escapejs }}', content: '{{ n.content|escapejs }}'}; $store.projectDetail.viewNoteOpen = true" class="inline-flex items-center px-2 py-1 text-xs font-medium rounded border border-blue-200 dark:border-blue-800 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20">View</button>
|
||
</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-700 flex items-center justify-between text-xs text-gray-500">
|
||
<span>{{ notes.start_index }}–{{ notes.end_index }} of {{ notes.paginator.count }}</span>
|
||
<div class="flex items-center gap-1">
|
||
{% if notes.has_previous %}<a href="?notes_page={{ notes.previous_page_number }}¬es_search={{ notes_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">‹</a>{% endif %}
|
||
<span class="px-2">{{ notes.number }}/{{ notes.paginator.num_pages }}</span>
|
||
{% if notes.has_next %}<a href="?notes_page={{ notes.next_page_number }}¬es_search={{ notes_search }}" class="px-2 py-1 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-800">›</a>{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- View Note Modal -->
|
||
<div x-cloak x-show="$store.projectDetail.viewNoteOpen" class="fixed inset-0 z-50 flex items-center justify-center">
|
||
<div class="fixed inset-0 bg-black/50" @click="$store.projectDetail.viewNoteOpen = 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 border-b border-gray-100 dark:border-gray-700">
|
||
<h3 class="text-xl font-bold text-gray-900 dark:text-white" x-text="$store.projectDetail.viewNoteData.title">Note</h3>
|
||
<button @click="$store.projectDetail.viewNoteOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||
<i class="fas fa-times text-lg"></i>
|
||
</button>
|
||
</div>
|
||
<div class="px-8 py-6 prose prose-sm dark:prose-invert max-w-none" x-html="$store.projectDetail.viewNoteData.content"></div>
|
||
<div class="px-8 py-5 flex justify-end gap-3 border-t border-gray-100 dark:border-gray-700">
|
||
<button type="button" @click="$store.projectDetail.viewNoteOpen = 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">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% include "components/external_dashboard_modal.html" with show_state="$store.projectDetail.externalModalOpen" %}
|
||
|
||
<!-- Manage Statuses Modal -->
|
||
<div x-cloak x-show="$store.projectDetail.manageStatusesOpen" class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
|
||
@keydown.escape.window="$store.projectDetail.manageStatusesOpen = false">
|
||
<div class="fixed inset-0 bg-black/50" @click="$store.projectDetail.manageStatusesOpen = 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-6xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Manage Ticket Statuses</h3>
|
||
<button @click="$store.projectDetail.manageStatusesOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><i class="fas fa-times text-xl"></i></button>
|
||
</div>
|
||
|
||
<!-- Create Status Form -->
|
||
<div class="rounded-lg bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 p-4 mb-4">
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">New Status</h4>
|
||
<form method="post" action="{% url 'ticket-status-create' %}" class="grid grid-cols-12 gap-3 items-end">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="project_id" value="{{ project.id }}">
|
||
<div class="col-span-3">
|
||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Name</label>
|
||
<input type="text" name="name" x-model="$store.projectDetail.createStatusName" 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">
|
||
</div>
|
||
<div class="col-span-2">
|
||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Color</label>
|
||
<div class="flex items-center gap-2">
|
||
<toolcool-color-picker popup-position="left" :color="$store.projectDetail.createStatusColor" @change="$store.projectDetail.createStatusColor = $event.detail.hex" class="shrink-0"></toolcool-color-picker>
|
||
<input type="text" name="color" x-model="$store.projectDetail.createStatusColor" 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 uppercase font-mono">
|
||
</div>
|
||
</div>
|
||
<div class="col-span-2">
|
||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Order</label>
|
||
<input type="number" name="sort_order" x-model="$store.projectDetail.createStatusOrder" 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">
|
||
</div>
|
||
<div class="col-span-3 flex items-center gap-3 pb-1">
|
||
<label class="flex items-center gap-2 cursor-pointer">
|
||
<input type="checkbox" name="is_completed" x-model="$store.projectDetail.createStatusCompleted" class="w-4 h-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
|
||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">Completed</span>
|
||
</label>
|
||
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors">Add</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Status List -->
|
||
<div class="rounded-xl bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||
<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 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">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</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-700">
|
||
{% for s in statuses %}
|
||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30" x-data="{ editing: false }">
|
||
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ 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">
|
||
<!-- Inline edit form -->
|
||
<form x-show="editing" x-cloak method="post" action="/ticket-statuses/{{ s.id }}/edit/" class="flex items-center gap-2">
|
||
{% csrf_token %}
|
||
<input type="text" name="name" x-model="$store.projectDetail.editStatusName" required
|
||
class="w-28 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-2 py-1 text-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
|
||
<div class="flex items-center gap-1">
|
||
<toolcool-color-picker popup-position="left" :color="$store.projectDetail.editStatusColor" @change="$store.projectDetail.editStatusColor = $event.detail.hex" class="shrink-0" style="height:28px;"></toolcool-color-picker>
|
||
<input type="text" name="color" x-model="$store.projectDetail.editStatusColor" required
|
||
class="w-20 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-2 py-1 text-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none uppercase font-mono">
|
||
</div>
|
||
<input type="number" name="sort_order" x-model="$store.projectDetail.editStatusOrder" required
|
||
class="w-16 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-2 py-1 text-xs focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
|
||
<label class="flex items-center gap-1 text-xs whitespace-nowrap">
|
||
<input type="checkbox" name="is_completed" x-model="$store.projectDetail.editStatusCompleted" class="w-3 h-3 text-blue-600 rounded border-gray-300">
|
||
Done
|
||
</label>
|
||
<button type="submit" class="px-2 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700"><i class="fas fa-check"></i></button>
|
||
<button type="button" @click="editing = false" class="px-2 py-1 text-xs font-medium text-gray-600 bg-gray-100 dark:bg-gray-800 rounded hover:bg-gray-200"><i class="fas fa-times"></i></button>
|
||
</form>
|
||
|
||
<!-- Action buttons -->
|
||
<div x-show="!editing" class="flex items-center gap-1">
|
||
<button type="button" @click="$store.projectDetail.editStatusId='{{ s.id }}'; $store.projectDetail.editStatusName='{{ s.name|escapejs }}'; $store.projectDetail.editStatusColor='{{ s.color|escapejs }}'; $store.projectDetail.editStatusOrder='{{ s.sort_order }}'; $store.projectDetail.editStatusCompleted={{ s.is_completed|lower }}; editing = true" class="inline-flex items-center gap-1 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">
|
||
<i class="fas fa-pen"></i>
|
||
</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:'Delete this status? This cannot be undone.', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1 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">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">No statuses found.</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Board Permissions Modal -->
|
||
<div x-cloak x-show="$store.projectDetail.permissionsOpen" class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
|
||
@keydown.escape.window="$store.projectDetail.permissionsOpen = false">
|
||
<div class="fixed inset-0 bg-black/50" @click="$store.projectDetail.permissionsOpen = 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-6xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Board Permissions</h3>
|
||
<button @click="$store.projectDetail.permissionsOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><i class="fas fa-times text-xl"></i></button>
|
||
</div>
|
||
|
||
<!-- Add Permission Form -->
|
||
<div class="rounded-lg bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 p-4 mb-4">
|
||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">Add / Update Permission</h4>
|
||
<form method="post" action="{% url 'board-permissions' project.id %}">
|
||
{% csrf_token %}
|
||
<div class="grid grid-cols-12 gap-3 items-start">
|
||
<div class="col-span-3">
|
||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Role</label>
|
||
<select name="role" 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">
|
||
<option value="">Select a role</option>
|
||
{% for role in role_choices %}
|
||
{% if role.slug not in admin_roles %}
|
||
<option value="{{ role.slug }}">{{ role.name }}</option>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-span-7">
|
||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Can move to</label>
|
||
<div class="grid grid-cols-3 gap-x-4 gap-y-1.5">
|
||
{% for s in project.ticket_statuses.all %}
|
||
<label class="flex items-center gap-2 cursor-pointer">
|
||
<input type="checkbox" name="move_to_statuses" value="{{ s.id }}"
|
||
class="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
|
||
<span class="w-2.5 h-2.5 rounded-full inline-block flex-shrink-0" style="background-color:{{ s.color }}"></span>
|
||
<span class="text-xs text-gray-700 dark:text-gray-300 truncate">{{ s.name }}</span>
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="col-span-2 pt-5">
|
||
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors w-full">Save</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Permissions List -->
|
||
<div class="rounded-xl bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||
<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 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Role</th>
|
||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Can move to</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-700">
|
||
{% for perm in permissions %}
|
||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ perm.role_display_name }}</td>
|
||
<td class="px-4 py-3">
|
||
<div class="flex flex-wrap gap-1.5">
|
||
{% for status in perm.move_to_statuses.all %}
|
||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium" style="background-color:{{ status.color }}20;color:{{ status.color }}">
|
||
<span class="w-1.5 h-1.5 rounded-full" style="background-color:{{ status.color }}"></span>
|
||
{{ status.name }}
|
||
</span>
|
||
{% empty %}
|
||
<span class="text-xs text-gray-400 italic">None</span>
|
||
{% endfor %}
|
||
</div>
|
||
</td>
|
||
<td class="px-4 py-3 text-right">
|
||
<form method="post" action="{% url 'board-permission-delete' project.id perm.id %}" class="inline">
|
||
{% csrf_token %}
|
||
<button type="button" @click="confirmAction({title:'Delete Permission', message:'Delete permission for <strong>' + '{{ perm.role_display_name|escapejs }}' + '</strong>?', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1 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">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="3" class="px-4 py-8 text-center text-sm text-gray-400">No permissions configured. All non-admin roles are restricted by default.</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-4"><i class="fas fa-info-circle"></i> Superadmin and manager roles always have full access. Select the statuses a developer role can move tickets to.</p>
|
||
</div>
|
||
</div>
|
||
|
||
{% include "projects/partials/board_automation_modal.html" with selected_project=project %}
|
||
|
||
{% endblock %}
|
||
|
||
{% block extra_scripts %}
|
||
<script>
|
||
document.addEventListener('alpine:init', () => {
|
||
Alpine.store('projectDetail', {
|
||
activeTab: 'statuses',
|
||
externalModalOpen: false,
|
||
epicsSelectedIds: [],
|
||
ticketsSelectedIds: [],
|
||
notesSelectedIds: [],
|
||
viewNoteOpen: false,
|
||
viewNoteData: { id: '', title: '', content: '' },
|
||
manageStatusesOpen: false,
|
||
permissionsOpen: false,
|
||
automationOpen: false,
|
||
createStatusName: '',
|
||
createStatusColor: '#6B7280',
|
||
createStatusOrder: '0',
|
||
createStatusCompleted: false,
|
||
editStatusId: null,
|
||
editStatusName: '',
|
||
editStatusColor: '',
|
||
editStatusOrder: '0',
|
||
editStatusCompleted: false,
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|