refactor(templates): remove duplicate and unused base template files

Remove duplicated base templates that caused conflicts and were no longer referenced.
This commit is contained in:
sankalp committed 2026-06-25 14:40:08 +05:45
1 parent 000bd08445
commit 9a6d3a3472
53 files changed
-2304

No files matched your search

-60
View File
@@ -1,60 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}ProMan{% endblock %}</title>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="https://unpkg.com/htmx.org@1.9.12" defer></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde@2.18.0/dist/easymde.min.css">
<script src="https://cdn.jsdelivr.net/npm/easymde@2.18.0/dist/easymde.min.js" defer></script>
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.min.js" defer></script>
{% load static tailwind_cli %}
{% tailwind_css %}
<style>
[x-cloak] { display: none !important; }
button:not(:disabled) { cursor: pointer; }
.border-subtle { border-color: rgba(0,0,0,0.06); }
.border-soft { border-color: rgba(0,0,0,0.10); }
.border-emphasis { border-color: rgba(0,0,0,0.18); }
.bg-canvas { background-color: #f6f8fa; }
.bg-elevated { background-color: #ffffff; }
.bg-hover { background-color: rgba(0,0,0,0.03); }
.stat-card {
background: #fff;
border: 1px solid rgba(0,0,0,0.06);
border-radius: 12px;
padding: 20px;
transition: border-color 0.15s;
}
.stat-card:hover { border-color: rgba(59,130,246,0.25); }
.card-surface {
background: #fff;
border: 1px solid rgba(0,0,0,0.06);
border-radius: 12px;
}
.text-primary { color: #0b1c33; }
.text-secondary { color: #3c4a5c; }
.text-tertiary { color: #6b7a8e; }
.text-muted { color: #9ca3af; }
@media (max-width: 1023px) {
.sidebar-mobile { transform: translateX(-100%); }
.sidebar-mobile.mobile-open { transform: translateX(0); }
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body class="min-h-screen bg-canvas antialiased" x-data="{ sidebarOpen: false }">
{% block content %}{% endblock %}
{% block extra_scripts %}{% endblock %}
</body>
</html>
-15
View File
@@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Project Management{% endblock %}</title>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
{% block extra_head %}{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
{% block extra_scripts %}{% endblock %}
</body>
</html>
-25
View File
@@ -1,25 +0,0 @@
{% extends "base.html" %}
{% block content %}
{% include "components/topbar.html" %}
<div class="flex">
{% include "components/sidebar.html" %}
<main class="flex-1 min-w-0 p-4 sm:p-6 lg:p-8 pt-20 lg:ml-64 transition-all duration-300">
{% if messages %}
<div class="space-y-2 mb-6">
{% for message in messages %}
<div class="flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium
{% if message.tags == 'error' %}bg-red-50 text-red-700 border border-red-200
{% elif message.tags == 'success' %}bg-green-50 text-green-700 border border-green-200
{% else %}bg-blue-50 text-blue-700 border border-blue-200{% endif %}">
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>{{ message }}</span>
</div>
{% endfor %}
</div>
{% endif %}
{% block content_inner %}{% endblock %}
</main>
</div>
{% endblock %}
-39
View File
@@ -1,39 +0,0 @@
<input type="checkbox" id="{{ modal_id }}" class="hidden delete-modal-toggle" />
<div class="delete-modal-container fixed inset-0 z-50 hidden items-center justify-center" id="{{ modal_id }}-container">
<div class="fixed inset-0 bg-black/20" onclick="closeDeleteModal('{{ modal_id }}')"></div>
<div class="relative bg-white rounded-xl max-w-sm w-full mx-4 border border-subtle p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</div>
<h3 class="text-base font-semibold text-primary mb-1">{{ title|default:"Confirm Delete" }}</h3>
<p class="text-sm text-tertiary mb-5">{{ message|default:"Are you sure you want to delete this item? This action cannot be undone." }}</p>
<form method="post" action="{{ delete_url }}">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors cursor-pointer">Delete</button>
<button type="button" onclick="closeDeleteModal('{{ modal_id }}')" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors cursor-pointer">Cancel</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var checkbox = document.getElementById('{{ modal_id }}');
if (checkbox) {
checkbox.addEventListener('change', function() {
var container = document.getElementById('{{ modal_id }}-container');
if (container) {
container.style.display = this.checked ? 'flex' : 'none';
}
});
}
});
function closeDeleteModal(id) {
var checkbox = document.getElementById(id);
if (checkbox) checkbox.checked = false;
var container = document.getElementById(id + '-container');
if (container) container.style.display = 'none';
}
</script>
-1
View File
@@ -1 +0,0 @@
<span class="block text-xs text-tertiary">(navbar.html replaced by topbar.html + sidebar.html)</span>
-33
View File
@@ -1,33 +0,0 @@
<aside class="sidebar-mobile fixed left-0 top-16 h-[calc(100vh-4rem)] w-64 bg-white border-r border-subtle transition-transform duration-300 z-20 overflow-y-auto"
:class="{ 'mobile-open': sidebarOpen }">
<nav class="p-3 space-y-1">
<div class="px-3 pt-2 pb-1">
<p class="text-[11px] font-semibold text-muted uppercase tracking-widest">Main</p>
</div>
{% include "components/sidebar_link.html" with url_name="home" label="Dashboard" icon="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" %}
{% include "components/sidebar_link.html" with url_name="project_list" label="Projects" icon="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" %}
{% include "components/sidebar_link.html" with url_name="board_list" label="Kanban" icon="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7" %}
{% include "components/sidebar_link.html" with url_name="issue_list" label="Issues" icon="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" %}
{% include "components/sidebar_link.html" with url_name="ticket_list" label="Tickets" icon="M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z" %}
<div class="px-3 pt-5 pb-1">
<p class="text-[11px] font-semibold text-muted uppercase tracking-widest">Finance</p>
</div>
{% include "components/sidebar_link.html" with url_name="invoice_list" label="Invoices" icon="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" %}
{% include "components/sidebar_link.html" with url_name="expense_list" label="Expenses" icon="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" %}
{% include "components/sidebar_link.html" with url_name="payment_list" label="Payments" icon="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" %}
{% include "components/sidebar_link.html" with url_name="team_payment_list" label="Payroll" icon="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" %}
{% if user.is_admin %}
<div class="px-3 pt-5 pb-1">
<p class="text-[11px] font-semibold text-muted uppercase tracking-widest">Admin</p>
</div>
{% include "components/sidebar_link.html" with url_name="activity_list" label="Activity Log" icon="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" %}
{% include "components/sidebar_link.html" with url_name="user_list" label="Users" icon="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" %}
{% endif %}
</nav>
</aside>
<div class="fixed inset-0 z-10 lg:hidden" style="display:none" x-show="sidebarOpen" @click="sidebarOpen = false" x-cloak></div>
-6
View File
@@ -1,6 +0,0 @@
<a href="{% url url_name %}"
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors
{% if url_name in request.path %}bg-blue-50 text-blue-700{% else %}text-secondary hover:bg-hover{% endif %}">
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{{ icon }}"/></svg>
{{ label }}
</a>
-20
View File
@@ -1,20 +0,0 @@
<header class="fixed top-0 left-0 right-0 h-16 bg-white/80 backdrop-blur-md border-b border-subtle z-30 flex items-center justify-between px-4 sm:px-6">
<div class="flex items-center gap-4">
<button @click="sidebarOpen = !sidebarOpen" class="lg:hidden p-2 text-secondary hover:text-primary rounded-lg hover:bg-hover transition-colors" aria-label="Toggle sidebar">
<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="M4 6h16M4 12h16M4 18h16"/></svg>
</button>
<a href="/" class="flex items-center gap-2 text-primary font-bold text-lg no-underline">
<svg class="w-7 h-7 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7"/></svg>
ProMan
</a>
</div>
<div class="flex items-center gap-3">
<span class="hidden sm:inline text-sm text-tertiary">{{ user.get_full_name|default:user.username }}</span>
<div class="w-8 h-8 rounded-full bg-blue-100 text-blue-700 flex items-center justify-center text-sm font-semibold">
{{ user.get_initials }}
</div>
<a href="{% url 'logout' %}" class="p-2 text-tertiary hover:text-secondary rounded-lg hover:bg-hover transition-colors" title="Log out">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
</a>
</div>
</header>
-30
View File
@@ -1,30 +0,0 @@
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md card-surface p-8">
<div class="text-center mb-8">
<div class="flex items-center justify-center gap-2 mb-4">
<svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7"/></svg>
<h1 class="text-2xl font-bold text-primary">ProMan</h1>
</div>
<p class="text-tertiary">Sign in to your account</p>
</div>
<form method="post" class="space-y-4">
{% csrf_token %}
<div class="space-y-1.5">
<label class="text-sm font-medium text-secondary">Username</label>
<input type="text" name="username" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors" required>
</div>
<div class="space-y-1.5">
<label class="text-sm font-medium text-secondary">Password</label>
<input type="password" name="password" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors" required>
</div>
<button type="submit" class="w-full px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors">Sign In</button>
</form>
<p class="text-center mt-6 text-sm text-tertiary">
Don't have an account? <a href="{% url 'register' %}" class="text-blue-600 hover:text-blue-800 font-medium">Register</a>
</p>
</div>
</div>
{% endblock %}
-39
View File
@@ -1,39 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Profile{% endblock %}
{% block content_inner %}
<div class="max-w-2xl mx-auto">
<div class="card-surface overflow-hidden">
<div class="bg-blue-600 px-6 py-12">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-full bg-white/20 flex items-center justify-center text-white text-2xl font-bold">
{{ profile_user.get_initials }}
</div>
<div class="text-white">
<h1 class="text-xl font-bold">{{ profile_user.get_full_name|default:profile_user.username }}</h1>
<p class="text-white/80 text-sm">{{ profile_user.get_role_display }}</p>
</div>
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-2 gap-3">
<div class="p-4 bg-hover rounded-xl">
<p class="text-xs text-muted font-semibold uppercase tracking-wider">Username</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.username }}</p>
</div>
<div class="p-4 bg-hover rounded-xl">
<p class="text-xs text-muted font-semibold uppercase tracking-wider">Email</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.email|default:"-" }}</p>
</div>
<div class="p-4 bg-hover rounded-xl">
<p class="text-xs text-muted font-semibold uppercase tracking-wider">Role</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.get_role_display }}</p>
</div>
<div class="p-4 bg-hover rounded-xl">
<p class="text-xs text-muted font-semibold uppercase tracking-wider">Member Since</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.date_joined|date:"M Y" }}</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
-29
View File
@@ -1,29 +0,0 @@
{% extends "base.html" %}
{% block title %}Register{% endblock %}
{% block content %}
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md card-surface p-8">
<div class="text-center mb-8">
<div class="flex items-center justify-center gap-2 mb-4">
<svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7"/></svg>
<h1 class="text-2xl font-bold text-primary">ProMan</h1>
</div>
<p class="text-tertiary">Create your account</p>
</div>
<form method="post" class="space-y-3">
{% csrf_token %}
{% for field in form %}
<div class="space-y-1.5">
<label class="text-sm font-medium text-secondary">{{ field.label }}</label>
<input type="{{ field.field.widget.input_type|default:'text' }}" name="{{ field.name }}" value="{{ field.value|default:'' }}" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors" {% if field.field.required %}required{% endif %}>
{% if field.errors %}<p class="text-xs text-red-600">{{ field.errors.0 }}</p>{% endif %}
</div>
{% endfor %}
<button type="submit" class="w-full px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors mt-2">Create Account</button>
</form>
<p class="text-center mt-6 text-sm text-tertiary">
Already have an account? <a href="{% url 'login' %}" class="text-blue-600 hover:text-blue-800 font-medium">Sign in</a>
</p>
</div>
</div>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete User{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete User</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete <strong>"{{ object.username }}"</strong>?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'user_list' %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-52
View File
@@ -1,52 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{{ profile_user.username }}{% endblock %}
{% block content_inner %}
<div class="max-w-2xl mx-auto">
<div class="card-surface overflow-hidden">
<div class="bg-blue-600 px-6 py-12">
<div class="flex items-center gap-4">
<div class="w-16 h-16 rounded-full bg-white/20 flex items-center justify-center text-white text-2xl font-bold">
{{ profile_user.first_name|first|default:profile_user.username|first|upper }}
</div>
<div class="text-white">
<h1 class="text-2xl font-bold">{{ profile_user.get_full_name|default:profile_user.username }}</h1>
<p class="text-white/80">{{ profile_user.get_role_display }}</p>
</div>
</div>
</div>
<div class="p-6 space-y-4">
<div class="grid grid-cols-2 gap-3 p-4 bg-hover rounded-xl">
<div>
<p class="text-xs text-muted font-medium uppercase tracking-wider">Username</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.username }}</p>
</div>
<div>
<p class="text-xs text-muted font-medium uppercase tracking-wider">Email</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.email|default:"-" }}</p>
</div>
<div>
<p class="text-xs text-muted font-medium uppercase tracking-wider">Role</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.get_role_display }}</p>
</div>
<div>
<p class="text-xs text-muted font-medium uppercase tracking-wider">Active</p>
<p class="text-sm font-semibold text-primary mt-1">{% if profile_user.is_active %}Yes{% else %}No{% endif %}</p>
</div>
<div>
<p class="text-xs text-muted font-medium uppercase tracking-wider">Member Since</p>
<p class="text-sm font-semibold text-primary mt-1">{{ profile_user.date_joined|date:"M Y" }}</p>
</div>
</div>
<div class="flex gap-3 pt-2">
<a href="{% url 'user_edit' profile_user.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Edit User</a>
<a href="{% url 'user_list' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Back</a>
{% if user.is_admin %}
<label for="delete-modal-user" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-600 text-white hover:bg-red-700 transition-colors cursor-pointer">Delete</label>
{% endif %}
</div>
</div>
</div>
</div>
{% url 'user_delete' profile_user.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-modal-user" delete_url=delete_url %}
{% endblock %}
-20
View File
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Edit User{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6">
<h1 class="text-lg font-bold text-primary mb-1">Edit User</h1>
<p class="text-sm text-tertiary mb-6">{{ object.username }}</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors">Save Changes</button>
<a href="{% url 'user_list' %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-61
View File
@@ -1,61 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Users{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Users</h1>
<p class="text-tertiary mt-1">Manage system users and their roles.</p>
</div>
<a href="{% url 'user_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New User
</a>
</div>
<div class="card-surface overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">User</th>
<th class="px-4 py-3 text-left">Email</th>
<th class="px-4 py-3 text-left">Role</th>
<th class="px-4 py-3 text-left">Status</th>
<th class="px-4 py-3 text-left"></th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-xs font-bold">{{ u.username|first|upper }}</div>
<a href="{% url 'user_detail' u.pk %}" class="font-medium text-primary hover:text-blue-600">{{ u.username }}</a>
</div>
</td>
<td class="px-4 py-3 text-secondary">{{ u.email|default:"-" }}</td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if u.role == 'admin' %}bg-sky-50 text-sky-700 border-sky-200{% elif u.role == 'client' %}bg-blue-50 text-blue-700 border-blue-200{% else %}bg-green-50 text-green-700 border-green-200{% endif %}">
{{ u.get_role_display }}
</span>
</td>
<td class="px-4 py-3">
{% if u.is_active %}
<span class="text-xs px-2 py-1 rounded-full font-medium border bg-green-50 text-green-700 border-green-200">Active</span>
{% else %}
<span class="text-xs px-2 py-1 rounded-full font-medium border bg-red-50 text-red-700 border-red-200">Inactive</span>
{% endif %}
</td>
<td class="px-4 py-3">
<div class="flex gap-1">
<a href="{% url 'user_detail' u.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">View</a>
<a href="{% url 'user_edit' u.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
<a href="{% url 'user_delete' u.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors">Delete</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
@@ -1,50 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Activity Log{% endblock %}
{% block content_inner %}
<div class="mb-6">
<h1 class="text-2xl font-bold text-primary">Activity Log</h1>
<p class="text-tertiary mt-1">System-wide audit trail of all actions.</p>
</div>
<div class="card-surface overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">Actor</th>
<th class="px-4 py-3 text-left">Action</th>
<th class="px-4 py-3 text-left">Object</th>
<th class="px-4 py-3 text-left">Timestamp</th>
</tr>
</thead>
<tbody>
{% for activity in activities %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3">
<div class="flex items-center gap-2">
<div class="w-7 h-7 rounded-full bg-blue-500 flex items-center justify-center text-white text-xs font-bold">{{ activity.actor.username|first|upper }}</div>
<span class="text-sm font-medium text-primary">{{ activity.actor.username }}</span>
</div>
</td>
<td class="px-4 py-3 text-secondary">{{ activity.verb }}</td>
<td class="px-4 py-3 text-tertiary">{{ activity.content_type }} #{{ activity.object_id }}</td>
<td class="px-4 py-3 text-tertiary">{{ activity.timestamp|date:"M d, Y H:i" }}</td>
</tr>
{% empty %}
<tr><td colspan="4" class="text-center py-12 text-tertiary">No activity recorded yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if page_obj.has_other_pages %}
<div class="flex justify-center items-center gap-2 mt-6">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Previous</a>
{% endif %}
<span class="text-sm text-tertiary">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Next</a>
{% endif %}
</div>
{% endif %}
{% endblock %}
-150
View File
@@ -1,150 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Dashboard{% endblock %}
{% block content_inner %}
<div class="mb-8">
<h1 class="text-xl font-bold text-primary">Welcome back, {{ user.first_name|default:user.username }}</h1>
<p class="text-sm text-tertiary mt-1.5">Here's what's happening across your projects.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 mb-8">
<div class="stat-card">
<p class="text-xs font-semibold text-muted uppercase tracking-wider">Projects</p>
<p class="text-3xl font-bold text-primary mt-0.5">{{ total_projects }}</p>
<div class="flex items-center gap-1.5 mt-2 text-xs">
<span class="w-1.5 h-1.5 rounded-full bg-green-500"></span>
<span class="font-medium text-green-600">{{ active_projects }}</span>
<span class="text-muted">active</span>
</div>
</div>
<div class="stat-card">
<p class="text-xs font-semibold text-muted uppercase tracking-wider">Open Issues</p>
<p class="text-3xl font-bold text-primary mt-0.5">{{ open_issues }}</p>
<div class="flex items-center gap-1.5 mt-2 text-xs">
<span class="text-tertiary">of</span>
<span class="font-medium text-secondary">{{ total_issues }}</span>
<span class="text-muted">total</span>
</div>
</div>
<div class="stat-card">
<p class="text-xs font-semibold text-muted uppercase tracking-wider">New Tickets</p>
<p class="text-3xl font-bold text-primary mt-0.5">{{ new_tickets }}</p>
<div class="flex items-center gap-1.5 mt-2 text-xs">
<span class="text-tertiary">of</span>
<span class="font-medium text-secondary">{{ total_tickets }}</span>
<span class="text-muted">total</span>
</div>
</div>
<div class="stat-card">
<p class="text-xs font-semibold text-muted uppercase tracking-wider">Active Now</p>
<p class="text-3xl font-bold text-primary mt-0.5">{{ active_projects }}</p>
<div class="flex items-center gap-1.5 mt-2 text-xs">
<span class="w-1.5 h-1.5 rounded-full bg-green-500"></span>
<span class="text-muted">projects running</span>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-8">
<div class="card-surface">
<div class="flex items-center justify-between px-5 py-3.5 border-b border-subtle">
<h2 class="text-sm font-semibold text-primary">Recent Projects</h2>
<a href="{% url 'project_list' %}" class="text-xs font-medium text-blue-600 hover:text-blue-800">View all</a>
</div>
<div class="p-2 space-y-0.5">
{% for project in projects %}
<a href="{% url 'project_detail' project.pk %}" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-hover transition-colors gap-3">
<div class="flex items-center gap-3 min-w-0">
<span class="w-2 h-2 rounded-full flex-shrink-0 {% if project.status == 'active' %}bg-green-500{% elif project.status == 'planning' %}bg-blue-500{% elif project.status == 'review' %}bg-amber-500{% else %}bg-gray-400{% endif %}"></span>
<div class="min-w-0">
<p class="text-sm font-medium text-primary truncate">{{ project.name }}</p>
<p class="text-xs text-tertiary">{{ project.client.username }}</p>
</div>
</div>
<span class="text-[11px] px-2.5 py-0.5 rounded-full font-medium border whitespace-nowrap {% if project.status == 'active' %}bg-green-50 text-green-700 border-green-200{% elif project.status == 'planning' %}bg-blue-50 text-blue-700 border-blue-200{% elif project.status == 'review' %}bg-amber-50 text-amber-700 border-amber-200{% else %}bg-gray-50 text-gray-600 border-gray-200{% endif %}">
{{ project.get_status_display }}
</span>
</a>
{% empty %}
<p class="text-sm text-muted text-center py-6">No projects yet.</p>
{% endfor %}
</div>
</div>
<div class="card-surface">
<div class="flex items-center justify-between px-5 py-3.5 border-b border-subtle">
<h2 class="text-sm font-semibold text-primary">Recent Issues</h2>
<a href="{% url 'issue_list' %}" class="text-xs font-medium text-blue-600 hover:text-blue-800">View all</a>
</div>
<div class="p-2 space-y-0.5">
{% for issue in recent_issues %}
<a href="{% url 'issue_detail' issue.pk %}" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-hover transition-colors gap-3">
<div class="flex items-center gap-3 min-w-0">
<span class="w-2 h-2 rounded-full flex-shrink-0 {% if issue.priority == 'high' %}bg-red-500{% elif issue.priority == 'medium' %}bg-amber-500{% else %}bg-blue-500{% endif %}"></span>
<div class="min-w-0">
<p class="text-sm font-medium text-primary truncate">{{ issue.title }}</p>
<p class="text-xs text-tertiary truncate">{{ issue.project.name }}</p>
</div>
</div>
<span class="text-[11px] px-2.5 py-0.5 rounded-full font-medium border whitespace-nowrap {% if issue.status == 'open' %}bg-blue-50 text-blue-700 border-blue-200{% elif issue.status == 'in_progress' %}bg-amber-50 text-amber-700 border-amber-200{% elif issue.status == 'resolved' %}bg-green-50 text-green-700 border-green-200{% else %}bg-gray-50 text-gray-600 border-gray-200{% endif %}">
{{ issue.get_status_display }}
</span>
</a>
{% empty %}
<p class="text-sm text-muted text-center py-6">No issues yet.</p>
{% endfor %}
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="card-surface">
<div class="flex items-center justify-between px-5 py-3.5 border-b border-subtle">
<h2 class="text-sm font-semibold text-primary">Recent Tickets</h2>
<a href="{% url 'ticket_list' %}" class="text-xs font-medium text-blue-600 hover:text-blue-800">View all</a>
</div>
<div class="p-2 space-y-0.5">
{% for ticket in recent_tickets %}
<a href="{% url 'ticket_detail' ticket.pk %}" class="flex items-center justify-between px-3 py-2.5 rounded-lg hover:bg-hover transition-colors gap-3">
<div class="flex items-center gap-3 min-w-0">
<span class="w-2 h-2 rounded-full flex-shrink-0 {% if ticket.ticket_type == 'bug' %}bg-red-500{% elif ticket.ticket_type == 'feature_request' %}bg-blue-500{% else %}bg-sky-500{% endif %}"></span>
<div class="min-w-0">
<p class="text-sm font-medium text-primary truncate">{{ ticket.title }}</p>
<p class="text-xs text-tertiary truncate">{{ ticket.project.name|default:"No project" }}</p>
</div>
</div>
<span class="text-[11px] px-2.5 py-0.5 rounded-full font-medium border whitespace-nowrap {% if ticket.ticket_type == 'bug' %}bg-red-50 text-red-700 border-red-200{% elif ticket.ticket_type == 'feature_request' %}bg-blue-50 text-blue-700 border-blue-200{% else %}bg-sky-50 text-sky-700 border-sky-200{% endif %}">
{{ ticket.get_ticket_type_display }}
</span>
</a>
{% empty %}
<p class="text-sm text-muted text-center py-6">No tickets yet.</p>
{% endfor %}
</div>
</div>
<div class="card-surface p-5">
<h2 class="text-sm font-semibold text-primary mb-4">Quick Actions</h2>
<div class="grid grid-cols-2 gap-2.5">
<a href="{% url 'project_create' %}" class="px-4 py-3 rounded-lg bg-blue-50 hover:bg-blue-100 transition-colors text-center">
<svg class="w-5 h-5 text-blue-600 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
<span class="text-xs font-medium text-blue-700">New Project</span>
</a>
<a href="{% url 'issue_create' %}" class="px-4 py-3 rounded-lg bg-amber-50 hover:bg-amber-100 transition-colors text-center">
<svg class="w-5 h-5 text-amber-600 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9"/></svg>
<span class="text-xs font-medium text-amber-700">New Issue</span>
</a>
<a href="{% url 'ticket_create' %}" class="px-4 py-3 rounded-lg bg-blue-50 hover:bg-blue-100 transition-colors text-center">
<svg class="w-5 h-5 text-blue-600 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z"/></svg>
<span class="text-xs font-medium text-blue-700">New Ticket</span>
</a>
<a href="{% url 'board_create' %}" class="px-4 py-3 rounded-lg bg-green-50 hover:bg-green-100 transition-colors text-center">
<svg class="w-5 h-5 text-green-600 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7"/></svg>
<span class="text-xs font-medium text-green-700">New Board</span>
</a>
</div>
</div>
</div>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Expense{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Expense</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete this <strong>${{ object.amount }} {{ object.category }}</strong> expense?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'expense_list' %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-20
View File
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}New Expense{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6">
<h1 class="text-xl font-bold text-primary mb-1">New Expense</h1>
<p class="text-sm text-tertiary mb-6">Log a new project expense.</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Submit</button>
<a href="{% url 'expense_list' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-44
View File
@@ -1,44 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Expenses{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Expenses</h1>
<p class="text-tertiary mt-1">Track project expenses.</p>
</div>
<a href="{% url 'expense_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Expense
</a>
</div>
<div class="card-surface overflow-hidden">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">Category</th>
<th class="px-4 py-3 text-left">Amount</th>
<th class="px-4 py-3 text-left">Project</th>
<th class="px-4 py-3 text-left">Raised By</th>
<th class="px-4 py-3 text-left">Date</th>
{% if user.is_admin %}<th class="px-4 py-3 text-left"></th>{% endif %}
</tr>
</thead>
<tbody>
{% for expense in expenses %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3 text-sm font-medium text-primary">{{ expense.category }}</td>
<td class="px-4 py-3 text-sm font-semibold text-primary">${{ expense.amount }}</td>
<td class="px-4 py-3 text-secondary">{{ expense.project.name }}</td>
<td class="px-4 py-3 text-secondary">{{ expense.raised_by.username }}</td>
<td class="px-4 py-3 text-tertiary">{{ expense.date|date:"M d" }}</td>
{% if user.is_admin %}
<td class="px-4 py-3"><a href="{% url 'expense_delete' expense.pk %}" class="text-xs text-red-500 hover:text-red-700">Delete</a></td>
{% endif %}
</tr>
{% empty %}
<tr><td colspan="{% if user.is_admin %}6{% else %}5{% endif %}" class="text-center py-12 text-tertiary">No expenses recorded.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Invoice{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Invoice</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete <strong>Invoice #{{ object.id }}</strong>?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'invoice_detail' object.pk %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -1,56 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Invoice #{{ invoice.id }}{% endblock %}
{% block content_inner %}
<div class="max-w-3xl mx-auto">
<div class="card-surface overflow-hidden mb-6">
<div class="p-6">
<div class="flex items-start justify-between mb-6">
<div>
<div class="flex items-center gap-2 text-sm text-tertiary mb-1">
<a href="{% url 'invoice_list' %}" class="hover:text-blue-600">Invoices</a>
<span>/</span>
</div>
<h1 class="text-2xl font-bold text-primary">Invoice #{{ invoice.id }}</h1>
</div>
<div class="flex items-center gap-2">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if invoice.status == 'paid' %}bg-green-50 text-green-700 border-green-200{% elif invoice.status == 'overdue' %}bg-red-50 text-red-700 border-red-200{% else %}bg-amber-50 text-amber-700 border-amber-200{% endif %}">
{{ invoice.get_status_display }}
</span>
{% if user.is_admin %}
<a href="{% url 'invoice_edit' invoice.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
<label for="delete-modal-invoice" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
{% endif %}
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 p-4 bg-hover rounded-xl mb-6">
<div><p class="text-xs text-muted uppercase font-medium">Project</p><p class="text-sm font-semibold text-primary mt-1">{{ invoice.project.name }}</p></div>
<div><p class="text-xs text-muted uppercase font-medium">Client</p><p class="text-sm font-semibold text-primary mt-1">{{ invoice.client.username }}</p></div>
<div><p class="text-xs text-muted uppercase font-medium">Amount</p><p class="text-lg font-bold text-primary mt-1">${{ invoice.amount }}</p></div>
<div><p class="text-xs text-muted uppercase font-medium">Due Date</p><p class="text-sm font-semibold text-primary mt-1">{{ invoice.due_date }}</p></div>
</div>
</div>
</div>
<div class="card-surface overflow-hidden">
<div class="px-6 py-4 border-b border-subtle flex items-center justify-between">
<h2 class="font-semibold text-primary">Payments</h2>
{% if user.is_admin %}
<a href="{% url 'payment_create' %}?invoice={{ invoice.pk }}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Record Payment</a>
{% endif %}
</div>
<div class="overflow-x-auto p-6">
<table class="w-full text-sm">
<thead><tr class="text-xs font-semibold text-muted uppercase bg-hover"><th class="px-4 py-3 text-left">Date</th><th class="px-4 py-3 text-left">Method</th><th class="px-4 py-3 text-left">Amount</th><th class="px-4 py-3 text-left">Reference</th></tr></thead>
<tbody>
{% for payment in payments %}
<tr class="border-t border-subtle"><td class="px-4 py-3 text-secondary">{{ payment.paid_at|date:"M d, Y H:i" }}</td><td class="px-4 py-3 text-secondary">{{ payment.method }}</td><td class="px-4 py-3 text-sm font-semibold text-primary">${{ payment.amount }}</td><td class="px-4 py-3 text-tertiary">{{ payment.reference|default:"-" }}</td></tr>
{% empty %}
<tr><td colspan="4" class="text-center py-8 text-muted">No payments recorded yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% url 'invoice_delete' invoice.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-modal-invoice" delete_url=delete_url %}
{% endblock %}
-20
View File
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}New Invoice{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6">
<h1 class="text-xl font-bold text-primary mb-1">New Invoice</h1>
<p class="text-sm text-tertiary mb-6">Create a new invoice for a client.</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Create</button>
<a href="{% url 'invoice_list' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-58
View File
@@ -1,58 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Invoices{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Invoices</h1>
<p class="text-tertiary mt-1">Track billing and payments.</p>
</div>
{% if user.is_admin %}
<a href="{% url 'invoice_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Invoice
</a>
{% endif %}
</div>
<div class="card-surface overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">#</th>
<th class="px-4 py-3 text-left">Project</th>
<th class="px-4 py-3 text-left">Amount</th>
<th class="px-4 py-3 text-left">Due</th>
<th class="px-4 py-3 text-left">Status</th>
<th class="px-4 py-3 text-left"></th>
</tr>
</thead>
<tbody>
{% for invoice in invoices %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3 font-mono text-sm font-medium text-primary">#{{ invoice.id }}</td>
<td class="px-4 py-3 text-secondary">{{ invoice.project.name }}</td>
<td class="px-4 py-3 text-sm font-semibold text-primary">${{ invoice.amount }}</td>
<td class="px-4 py-3 text-secondary">{{ invoice.due_date }}</td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if invoice.status == 'paid' %}bg-green-50 text-green-700 border-green-200{% elif invoice.status == 'overdue' %}bg-red-50 text-red-700 border-red-200{% else %}bg-amber-50 text-amber-700 border-amber-200{% endif %}">
{{ invoice.get_status_display }}
</span>
</td>
<td class="px-4 py-3">
<div class="flex gap-1">
<a href="{% url 'invoice_detail' invoice.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">View</a>
{% if user.is_admin %}
<a href="{% url 'invoice_edit' invoice.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
<a href="{% url 'invoice_delete' invoice.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors">Delete</a>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="6" class="text-center py-12 text-tertiary">No invoices found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Payment{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Payment</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete this <strong>${{ object.amount }}</strong> payment?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'payment_list' %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-20
View File
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Record Payment{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6">
<h1 class="text-lg font-bold text-primary mb-1">Record Payment</h1>
<p class="text-sm text-tertiary mb-6">Record a payment against an invoice.</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors">Record</button>
<a href="{% url 'invoice_list' %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-44
View File
@@ -1,44 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Payments{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Payments</h1>
<p class="text-tertiary mt-1">All invoice payments.</p>
</div>
<a href="{% url 'payment_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Payment
</a>
</div>
<div class="card-surface overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">Invoice</th>
<th class="px-4 py-3 text-left">Date</th>
<th class="px-4 py-3 text-left">Method</th>
<th class="px-4 py-3 text-left">Amount</th>
<th class="px-4 py-3 text-left">Reference</th>
<th class="px-4 py-3 text-left"></th>
</tr>
</thead>
<tbody>
{% for payment in payments %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3 text-sm font-medium text-primary">#{{ payment.invoice.id }}</td>
<td class="px-4 py-3 text-secondary">{{ payment.paid_at|date:"M d, Y H:i" }}</td>
<td class="px-4 py-3 text-secondary">{{ payment.method }}</td>
<td class="px-4 py-3 text-sm font-semibold text-primary">${{ payment.amount }}</td>
<td class="px-4 py-3 text-tertiary">{{ payment.reference|default:"-" }}</td>
<td class="px-4 py-3"><a href="{% url 'payment_delete' payment.pk %}" class="text-xs text-red-500 hover:text-red-700">Delete</a></td>
</tr>
{% empty %}
<tr><td colspan="6" class="text-center py-12 text-tertiary">No payments recorded.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Payment{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Payroll Payment</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete the <strong>${{ object.amount }}</strong> payment to <strong>{{ object.member.username }}</strong>?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'team_payment_list' %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}New Team Payment{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6">
<h1 class="text-lg font-bold text-primary mb-1">Record Team Payment</h1>
<p class="text-sm text-tertiary mb-6">Record a payroll payment for a team member.</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors">Save</button>
<a href="{% url 'team_payment_list' %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -1,57 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Team Payments{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Payroll</h1>
<p class="text-tertiary mt-1">Team member payments and salary records.</p>
</div>
{% if user.is_admin %}
<a href="{% url 'team_payment_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Payment
</a>
{% endif %}
</div>
<div class="card-surface overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">Member</th>
<th class="px-4 py-3 text-left">Period</th>
<th class="px-4 py-3 text-left">Amount</th>
<th class="px-4 py-3 text-left">Status</th>
{% if user.is_admin %}<th class="px-4 py-3 text-left"></th>{% endif %}
</tr>
</thead>
<tbody>
{% for payment in payments %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3">
<div class="flex items-center gap-2">
<div class="w-7 h-7 rounded-full bg-blue-500 flex items-center justify-center text-white text-xs font-bold">{{ payment.member.username|first|upper }}</div>
<span class="text-sm font-medium text-primary">{{ payment.member.username }}</span>
</div>
</td>
<td class="px-4 py-3 text-secondary">{{ payment.period|date:"F Y" }}</td>
<td class="px-4 py-3 text-sm font-semibold text-primary">${{ payment.amount }}</td>
<td class="px-4 py-3">
{% if payment.paid_at %}
<span class="text-xs px-2 py-1 rounded-full font-medium border bg-green-50 text-green-700 border-green-200">Paid</span>
{% else %}
<span class="text-xs px-2 py-1 rounded-full font-medium border bg-amber-50 text-amber-700 border-amber-200">Pending</span>
{% endif %}
</td>
{% if user.is_admin %}
<td class="px-4 py-3"><a href="{% url 'team_payment_delete' payment.pk %}" class="text-xs text-red-500 hover:text-red-700">Delete</a></td>
{% endif %}
</tr>
{% empty %}
<tr><td colspan="{% if user.is_admin %}5{% else %}4{% endif %}" class="text-center py-12 text-tertiary">No payments recorded.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Issue{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Issue</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete <strong>"{{ object.title }}"</strong>?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'issue_detail' object.pk %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-76
View File
@@ -1,76 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{{ issue.title }}{% endblock %}
{% block content_inner %}
<div class="max-w-4xl mx-auto">
<div class="card-surface overflow-hidden mb-6">
<div class="p-6">
<div class="flex items-start justify-between mb-4">
<div>
<div class="flex items-center gap-2 text-sm text-tertiary mb-1">
<a href="{% url 'issue_list' %}" class="hover:text-blue-600">Issues</a>
<span>/</span>
<span class="text-primary">{{ issue.title|truncatechars:30 }}</span>
</div>
<h1 class="text-2xl font-bold text-primary">{{ issue.title }}</h1>
</div>
<div class="flex gap-2">
<a href="{% url 'issue_edit' issue.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
{% if user.is_admin %}
<label for="delete-modal-issue" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
{% endif %}
</div>
</div>
<div class="flex flex-wrap gap-2 mb-4">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if issue.status == 'open' %}bg-blue-50 text-blue-700 border-blue-200{% elif issue.status == 'in_progress' %}bg-amber-50 text-amber-700 border-amber-200{% elif issue.status == 'resolved' %}bg-green-50 text-green-700 border-green-200{% else %}bg-gray-50 text-gray-700 border-gray-200{% endif %}">
{{ issue.get_status_display }}
</span>
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if issue.priority == 'high' %}bg-red-50 text-red-700 border-red-200{% elif issue.priority == 'medium' %}bg-amber-50 text-amber-700 border-amber-200{% else %}bg-blue-50 text-blue-700 border-blue-200{% endif %}">
{{ issue.get_priority_display }}
</span>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 p-4 bg-hover rounded-xl mb-6">
<div><p class="text-xs text-muted uppercase font-medium">Project</p><p class="text-sm font-semibold text-primary mt-0.5"><a href="{% url 'project_detail' issue.project.pk %}" class="hover:text-blue-600">{{ issue.project.name }}</a></p></div>
<div><p class="text-xs text-muted uppercase font-medium">Reporter</p><p class="text-sm font-semibold text-primary mt-0.5">{{ issue.reporter.username }}</p></div>
<div><p class="text-xs text-muted uppercase font-medium">Assignee</p><p class="text-sm font-semibold text-primary mt-0.5">{{ issue.assignee.username|default:"Unassigned" }}</p></div>
</div>
<div class="prose prose-sm max-w-none">
<div class="text-secondary">{{ issue.description|safe }}</div>
</div>
</div>
</div>
<div class="card-surface overflow-hidden">
<div class="px-6 py-4 border-b border-subtle">
<h2 class="font-semibold text-primary">Comments ({{ comments.count }})</h2>
</div>
<div class="p-6">
<div id="comment-list" class="space-y-4 mb-6">
{% for comment in comments %}
<div class="flex gap-3">
<div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white text-xs font-bold flex-shrink-0">{{ comment.author.username|first|upper }}</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-primary">{{ comment.author.username }}</span>
<span class="text-xs text-muted">{{ comment.created_at|timesince }} ago</span>
</div>
<p class="text-sm text-secondary">{{ comment.body }}</p>
</div>
</div>
{% empty %}
<p class="text-sm text-muted text-center py-4">No comments yet. Be the first to comment.</p>
{% endfor %}
</div>
<form method="post" action="{% url 'issue_comment' issue.pk %}"
hx-target="#comment-list" hx-swap="innerHTML"
class="border-t border-subtle pt-4">
{% csrf_token %}
<label class="text-sm font-medium text-secondary mb-2 block">Add a comment</label>
<textarea name="body" rows="2" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors resize-y" placeholder="Write your comment..." required></textarea>
<button type="submit" class="mt-2 px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Post Comment</button>
</form>
</div>
</div>
</div>
{% url 'issue_delete' issue.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-modal-issue" delete_url=delete_url %}
{% endblock %}
-56
View File
@@ -1,56 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{% if object %}Edit{% else %}New{% endif %} Issue{% endblock %}
{% block content_inner %}
<div class="max-w-2xl mx-auto">
<div class="card-surface p-6">
<h1 class="text-xl font-bold text-primary mb-1">{% if object %}Edit{% else %}New{% endif %} Issue</h1>
<p class="text-sm text-tertiary mb-6">{% if object %}Update the issue details.{% else %}Create a new issue to track work.{% endif %}</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Save</button>
<a href="{% url 'issue_list' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const descField = document.querySelector('textarea[name="description"]');
if (descField) {
const container = document.createElement('div');
container.style.height = '400px';
container.className = 'mb-4 rounded-xl';
descField.parentNode.insertBefore(container, descField);
descField.style.display = 'none';
const quill = new Quill(container, {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
[{ 'color': [] }, { 'background': [] }],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'align': [] }],
['link', 'image', 'video'],
['blockquote', 'code-block'],
['clean']
]
}
});
quill.root.innerHTML = descField.value;
document.querySelector('form').addEventListener('submit', function() {
descField.value = quill.root.innerHTML;
});
}
});
</script>
{% endblock %}
-68
View File
@@ -1,68 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Issues{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Issues</h1>
<p class="text-tertiary mt-1">Track bugs, features, and tasks.</p>
</div>
<a href="{% url 'issue_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Issue
</a>
</div>
<div class="card-surface overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">Title</th>
<th class="px-4 py-3 text-left">Project</th>
<th class="px-4 py-3 text-left">Status</th>
<th class="px-4 py-3 text-left">Priority</th>
<th class="px-4 py-3 text-left">Assignee</th>
<th class="px-4 py-3 text-left">Created</th>
{% if user.is_admin %}<th class="px-4 py-3 text-left">Actions</th>{% endif %}
</tr>
</thead>
<tbody>
{% for issue in issues %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3">
<a href="{% url 'issue_detail' issue.pk %}" class="font-medium text-primary hover:text-blue-600">{{ issue.title }}</a>
</td>
<td class="px-4 py-3 text-secondary">{{ issue.project.name }}</td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if issue.status == 'open' %}bg-blue-50 text-blue-700 border-blue-200{% elif issue.status == 'in_progress' %}bg-amber-50 text-amber-700 border-amber-200{% elif issue.status == 'resolved' %}bg-green-50 text-green-700 border-green-200{% else %}bg-gray-50 text-gray-700 border-gray-200{% endif %}">
{{ issue.get_status_display }}
</span>
</td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if issue.priority == 'high' %}bg-red-50 text-red-700 border-red-200{% elif issue.priority == 'medium' %}bg-amber-50 text-amber-700 border-amber-200{% else %}bg-blue-50 text-blue-700 border-blue-200{% endif %}">
{{ issue.get_priority_display }}
</span>
</td>
<td class="px-4 py-3 text-secondary">{{ issue.assignee.username|default:"-" }}</td>
<td class="px-4 py-3 text-tertiary">{{ issue.created_at|date:"M d" }}</td>
{% if user.is_admin %}
<td class="px-4 py-3">
<label for="delete-issue-{{ issue.pk }}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
</td>
{% endif %}
</tr>
{% empty %}
<tr><td colspan="{% if user.is_admin %}7{% else %}6{% endif %}" class="text-center py-12 text-tertiary">No issues found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if user.is_admin %}
{% for issue in issues %}
{% with issue_id=issue.pk|stringformat:"s" %}
{% url 'issue_delete' issue.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-issue-"|add:issue_id title="Delete Issue" message="Are you sure you want to delete this issue? This action cannot be undone." delete_url=delete_url %}
{% endwith %}
{% endfor %}
{% endif %}
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Board{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Board</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete <strong>"{{ object.name }}"</strong>?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'board_detail' object.pk %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-170
View File
@@ -1,170 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{{ board.name }}{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<div class="flex items-center gap-2 text-sm text-tertiary mb-1">
<a href="{% url 'board_list' %}" class="hover:text-blue-600">Boards</a>
<span>/</span>
</div>
<h1 class="text-2xl font-bold text-primary">{{ board.name }}</h1>
{% if board.project %}<p class="text-sm text-tertiary">Project: {{ board.project.name }}</p>{% endif %}
</div>
<div class="flex gap-2">
<a href="{% url 'board_edit' board.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
{% if user.is_admin %}
<label for="delete-modal-board" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
{% endif %}
</div>
</div>
<div class="flex gap-4 overflow-x-auto pb-6 min-h-[60vh]" id="kanban-board">
{% for column in columns %}
<div class="bg-hover rounded-xl min-w-[280px] w-[280px] flex-shrink-0 flex flex-col">
<div class="flex items-center justify-between px-4 py-3">
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full {% cycle 'bg-blue-400' 'bg-amber-400' 'bg-green-400' 'bg-sky-400' 'bg-blue-400' 'bg-pink-400' %}"></div>
<h3 class="font-semibold text-secondary text-sm">{{ column.name }}</h3>
<span class="text-xs px-2 py-0.5 rounded-full bg-white text-tertiary font-medium">{{ column.cards.count }}</span>
</div>
</div>
<div class="flex-1 px-3 pb-3 space-y-2 min-h-[100px]"
id="column-{{ column.pk }}"
data-sortable-column="{{ column.pk }}">
{% for card in column.cards.all %}
{% include "partials/kanban/card.html" %}
{% endfor %}
</div>
<div class="px-3 pb-3">
<a href="{% url 'card_create' board.pk column.pk %}" class="w-full px-3 py-1.5 rounded-lg text-sm font-medium text-tertiary hover:bg-hover border border-soft transition-colors flex items-center justify-center gap-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
Add Card
</a>
</div>
</div>
{% endfor %}
<div class="flex-shrink-0 min-w-[280px] w-[280px]">
<div class="bg-hover border-2 border-dashed border-soft rounded-xl p-4 h-full">
<form method="post" action="{% url 'column_create' board.pk %}">
{% csrf_token %}
<div class="flex items-center gap-2">
<input type="text" name="name" placeholder="New column name..." class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors" required>
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
</button>
</div>
</form>
</div>
</div>
</div>
{% url 'board_delete' board.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-modal-board" delete_url=delete_url %}
<!-- Card Delete Modal -->
<div class="fixed inset-0 z-50 flex items-center justify-center" id="card-delete-modal-container" style="display:none">
<div class="fixed inset-0 bg-black/20" onclick="closeCardDeleteModal()"></div>
<div class="relative bg-white rounded-xl max-w-sm w-full mx-4 border border-subtle p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h3 class="text-base font-semibold text-primary mb-1">Delete Card</h3>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete this card?</p>
<form id="card-delete-form" method="post" action="">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<button type="button" onclick="closeCardDeleteModal()" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</button>
</div>
</form>
</div>
</div>
<style>
.sortable-ghost { opacity: 0.3; }
.sortable-drag {
box-shadow: 0 20px 60px rgba(0,0,0,0.2) !important;
transform: rotate(3deg) !important;
}
</style>
{% endblock %}
{% block extra_scripts %}
<script>
function openCardDeleteModal(actionUrl) {
document.getElementById('card-delete-form').action = actionUrl;
document.getElementById('card-delete-modal-container').style.display = 'flex';
}
function closeCardDeleteModal() {
document.getElementById('card-delete-modal-container').style.display = 'none';
}
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function initKanbanSortable() {
const containers = document.querySelectorAll('[data-sortable-column]');
const csrftoken = getCookie('csrftoken');
if (!csrftoken) return;
containers.forEach(function(container) {
if (container.sortableInstance) return;
container.sortableInstance = new Sortable(container, {
group: {
name: 'kanban',
pull: true,
put: true
},
animation: 150,
ghostClass: 'sortable-ghost',
dragClass: 'sortable-drag',
delay: 150,
delayOnTouchOnly: true,
filter: '.no-drag, a, button, input, textarea, select',
preventOnFilter: false,
scroll: true,
bubbleScroll: true,
onEnd: function(evt) {
var item = evt.item;
var to = evt.to;
if (!item || !to) return;
var cardId = item.getAttribute('data-card-id');
var newColumnId = to.getAttribute('data-sortable-column');
var order = evt.newIndex;
if (!cardId || !newColumnId || order === undefined) return;
fetch('/cards/' + cardId + '/move/', {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'column_id=' + newColumnId + '&order=' + order
}).then(function(r) {
if (!r.ok) location.reload();
}).catch(function() {
location.reload();
});
}
});
});
}
document.addEventListener('DOMContentLoaded', initKanbanSortable);
document.addEventListener('htmx:afterSwap', initKanbanSortable);
</script>
{% endblock %}
-20
View File
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{% if object %}Edit{% else %}New{% endif %} Board{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6">
<h1 class="text-xl font-bold text-primary mb-1">{% if object %}Edit{% else %}New{% endif %} Board</h1>
<p class="text-sm text-tertiary mb-6">{% if object %}Update your board settings.{% else %}Create a new kanban board to organize your tasks.{% endif %}</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Save</button>
<a href="{% url 'board_list' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
-46
View File
@@ -1,46 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Kanban Boards{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Kanban Boards</h1>
<p class="text-tertiary mt-1">Manage your personal task boards.</p>
</div>
<a href="{% url 'board_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Board
</a>
</div>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{% for board in boards %}
<div class="card-surface hover:border-blue-300 transition-colors overflow-hidden">
<div class="p-5">
<div class="flex items-center justify-between mb-3">
<div class="w-10 h-10 rounded-xl bg-blue-600 flex items-center justify-center text-white font-bold text-sm">B</div>
<span class="text-xs text-muted">{{ board.columns.count }} columns</span>
</div>
<a href="{% url 'board_detail' board.pk %}" class="text-lg font-semibold text-primary hover:text-blue-600 transition-colors">{{ board.name }}</a>
<p class="text-sm text-tertiary mt-1">Owner: {{ board.owner.username }}</p>
{% if board.project %}<p class="text-xs text-muted mt-1">Project: {{ board.project.name }}</p>{% endif %}
</div>
<div class="px-5 py-3 bg-hover border-t border-subtle flex gap-2">
<a href="{% url 'board_detail' board.pk %}" class="text-sm text-blue-600 hover:text-blue-800 font-medium">Open</a>
<a href="{% url 'board_edit' board.pk %}" class="text-sm text-tertiary hover:text-secondary ml-auto">Edit</a>
<label for="delete-board-{{ board.pk }}" class="text-sm text-red-600 hover:text-red-800 font-medium cursor-pointer">Delete</label>
</div>
</div>
{% empty %}
<div class="col-span-full card-surface p-12 text-center">
<svg class="w-12 h-12 text-muted mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7"/></svg>
<p class="text-tertiary">No boards yet.</p>
<a href="{% url 'board_create' %}" class="inline-block mt-3 px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Create your first board</a>
</div>
{% endfor %}
</div>
{% for board in boards %}
{% with board_id=board.pk|stringformat:"s" %}
{% url 'board_delete' board.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-board-"|add:board_id title="Delete Board" message="Are you sure you want to delete this board? This action cannot be undone." delete_url=delete_url %}
{% endwith %}
{% endfor %}
{% endblock %}
-104
View File
@@ -1,104 +0,0 @@
{% extends "base_authenticated.html" %}
{% load form_helpers %}
{% block title %}{{ card.title }}{% endblock %}
{% block content_inner %}
<div class="max-w-4xl mx-auto">
<div class="flex items-center gap-2 text-sm text-tertiary mb-4">
<a href="{% url 'board_list' %}" class="hover:text-blue-600">Boards</a>
<span>/</span>
<a href="{% url 'board_detail' card.column.board.pk %}" class="hover:text-blue-600">{{ card.column.board.name }}</a>
<span>/</span>
<span class="text-primary">{{ card.title|truncatechars:30 }}</span>
</div>
<div class="card-surface overflow-hidden mb-6">
<div class="p-6">
<div class="flex items-start justify-between mb-4">
<div>
<h1 class="text-2xl font-bold text-primary">{{ card.title }}</h1>
<div class="flex items-center gap-2 mt-2">
{% if card.priority == 'urgent' %}
<span class="text-xs px-2 py-1 rounded-full font-semibold border bg-red-50 text-red-700 border-red-200">Urgent</span>
{% elif card.priority == 'high' %}
<span class="text-xs px-2 py-1 rounded-full font-semibold border bg-amber-50 text-amber-700 border-amber-200">High</span>
{% elif card.priority == 'medium' %}
<span class="text-xs px-2 py-1 rounded-full font-semibold border bg-blue-50 text-blue-700 border-blue-200">Medium</span>
{% else %}
<span class="text-xs px-2 py-1 rounded-full font-semibold border bg-gray-50 text-gray-600 border-gray-200">Low</span>
{% endif %}
<span class="text-xs text-muted">{{ card.column.name }} · {{ card.column.board.name }}</span>
</div>
</div>
<div class="flex gap-2">
<a href="{% url 'card_edit' card.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
<label for="card-delete-modal" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 p-4 bg-hover rounded-xl mb-6">
<div>
<p class="text-[10px] text-muted uppercase font-semibold tracking-wider">Assignee</p>
<p class="text-sm font-medium text-primary mt-0.5">{{ card.assignee.username|default:"Unassigned" }}</p>
</div>
<div>
<p class="text-[10px] text-muted uppercase font-semibold tracking-wider">Start</p>
<p class="text-sm font-medium text-primary mt-0.5">{{ card.start_date|date:"M d, Y"|default:"-" }}</p>
</div>
<div>
<p class="text-[10px] text-muted uppercase font-semibold tracking-wider">End</p>
<p class="text-sm font-medium text-primary mt-0.5">{{ card.end_date|date:"M d, Y"|default:"-" }}</p>
</div>
<div>
<p class="text-[10px] text-muted uppercase font-semibold tracking-wider">Due</p>
<p class="text-sm font-medium text-primary mt-0.5">{{ card.due_date|date:"M d, Y"|default:"-" }}</p>
</div>
</div>
{% if card.description %}
<div class="prose prose-sm max-w-none text-secondary mb-6">
{{ card.description|render_markdown }}
</div>
{% endif %}
</div>
</div>
<div class="card-surface overflow-hidden">
<div class="px-6 py-4 border-b border-subtle flex items-center justify-between">
<h2 class="font-semibold text-primary">Comments ({{ comments.count }})</h2>
</div>
<div class="p-6">
<div id="comment-list" class="space-y-4 mb-6">
{% for comment in comments %}
{% include "partials/kanban/comment.html" %}
{% empty %}
<p class="text-sm text-muted text-center py-4">No comments yet.</p>
{% endfor %}
</div>
<form method="post" action="{% url 'card_comment' card.pk %}"
hx-target="#comment-list" hx-swap="beforeend"
class="border-t border-subtle pt-4">
{% csrf_token %}
<label class="text-sm font-medium text-secondary mb-2 block">Add a comment</label>
<textarea name="body" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors resize-y min-h-[100px]" placeholder="Write your comment... (Markdown supported)"></textarea>
<button type="submit" class="mt-2 px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Post Comment</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% include "components/delete_modal.html" with modal_id="card-delete-modal" title="Delete Card" message="Are you sure you want to delete this card? This action cannot be undone." delete_url="/cards/{{ card.pk }}/delete/" %}
{% block extra_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const commentField = document.querySelector('textarea[name="body"]');
if (commentField) {
new EasyMDE({
element: commentField,
spellChecker: false,
toolbar: ['bold', 'italic', 'quote', '|', 'code', 'link', '|', 'preview'],
});
}
});
</script>
{% endblock %}
-94
View File
@@ -1,94 +0,0 @@
{% extends "base_authenticated.html" %}
{% load form_helpers %}
{% block title %}{% if is_create %}New Card{% else %}Edit Card{% endif %}{% endblock %}
{% block content_inner %}
<div class="max-w-3xl mx-auto">
<div class="flex items-center gap-2 text-sm text-tertiary mb-4">
<a href="{% url 'board_list' %}" class="hover:text-blue-600">Boards</a>
<span>/</span>
<a href="{% url 'board_detail' board.pk %}" class="hover:text-blue-600">{{ board.name }}</a>
<span>/</span>
<span class="text-primary">{% if is_create %}New Card{% else %}{{ object.title|truncatechars:30 }}{% endif %}</span>
</div>
<div class="card-surface overflow-hidden">
<div class="p-6">
<h1 class="text-xl font-bold text-primary mb-1">{% if is_create %}New Card{% else %}Edit Card{% endif %}</h1>
<p class="text-sm text-tertiary mb-6">Column: <strong>{{ column.name }}</strong></p>
<form method="post">
{% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% for field in form %}
<div class="{% if field.name == 'title' or field.name == 'description' %}md:col-span-2{% endif %}">
{% include "partials/form_field.html" %}
</div>
{% endfor %}
</div>
<div class="flex gap-3 pt-4 mt-4 border-t border-subtle">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
{% if is_create %}Create Card{% else %}Save Changes{% endif %}
</button>
<a href="{% url 'board_detail' board.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Cancel</a>
{% if not is_create %}
<label for="card-delete-modal" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors ml-auto cursor-pointer">Delete</label>
{% endif %}
</div>
</form>
</div>
</div>
{% if not is_create and comments is not None %}
<div class="card-surface overflow-hidden mt-6">
<div class="px-6 py-4 border-b border-subtle">
<h2 class="font-semibold text-primary">Comments ({{ comments.count }})</h2>
</div>
<div class="p-6">
<div id="comment-list" class="space-y-4 mb-6">
{% for comment in comments %}
{% include "partials/kanban/comment.html" %}
{% empty %}
<p class="text-sm text-muted text-center py-4">No comments yet.</p>
{% endfor %}
</div>
<form method="post" action="{% url 'card_comment' object.pk %}"
hx-target="#comment-list" hx-swap="beforeend"
class="border-t border-subtle pt-4">
{% csrf_token %}
<label class="text-sm font-medium text-secondary mb-2 block">Add a comment</label>
<textarea name="body" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors resize-y min-h-[100px]" placeholder="Write your comment... (Markdown supported)"></textarea>
<button type="submit" class="mt-2 px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Post Comment</button>
</form>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% if not is_create %}
{% include "components/delete_modal.html" with modal_id="card-delete-modal" title="Delete Card" message="Are you sure you want to delete this card? This action cannot be undone." delete_url="/cards/{{ object.pk }}/delete/" %}
{% endif %}
{% block extra_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const desc = document.querySelector('textarea[name="description"]');
if (desc) {
new EasyMDE({
element: desc,
spellChecker: false,
autosave: { enabled: false },
toolbar: ['bold', 'italic', 'heading', '|', 'quote', 'unordered-list', 'ordered-list', '|', 'link', 'code', 'table', '|', 'preview', 'guide'],
});
}
{% if not is_create %}
const commentFields = document.querySelectorAll('textarea[name="body"]');
commentFields.forEach(el => {
new EasyMDE({
element: el,
spellChecker: false,
toolbar: ['bold', 'italic', 'quote', '|', 'code', 'link', '|', 'preview'],
});
});
{% endif %}
});
</script>
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Project{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Project</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete <strong>"{{ object.name }}"</strong>? This action cannot be undone.</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'project_detail' object.pk %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -1,93 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{{ project.name }}{% endblock %}
{% block content_inner %}
<div class="card-surface overflow-hidden mb-6">
<div class="bg-blue-600 px-6 py-8">
<div class="flex items-start justify-between">
<div>
<h1 class="text-2xl font-bold text-white">{{ project.name }}</h1>
<div class="flex items-center gap-3 mt-2">
<span class="text-xs px-2 py-1 rounded-full font-medium border bg-white/20 text-white border-white/30">{{ project.get_status_display }}</span>
<span class="text-white/80 text-sm">Client: {{ project.client.username }}</span>
</div>
</div>
{% if user.is_admin %}
<div class="flex gap-2">
<a href="{% url 'project_edit' project.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-white bg-white/20 hover:bg-white/30 transition-colors">Edit</a>
<label for="delete-modal-project" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
</div>
{% endif %}
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 p-4 bg-hover rounded-xl mb-6">
<div>
<p class="text-xs text-muted uppercase tracking-wider font-medium">Open Issues</p>
<p class="text-2xl font-bold text-primary mt-1">{{ open_issues }}</p>
</div>
<div>
<p class="text-xs text-muted uppercase tracking-wider font-medium">Members</p>
<p class="text-2xl font-bold text-primary mt-1">{{ project.members.count }}</p>
</div>
<div>
<p class="text-xs text-muted uppercase tracking-wider font-medium">Created</p>
<p class="text-2xl font-bold text-primary mt-1">{{ project.created_at|date:"M Y" }}</p>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold text-primary mb-3">Team Members</h3>
<div class="flex flex-wrap gap-2">
{% for member in project.members.all %}
<div class="flex items-center gap-2 px-3 py-1.5 bg-hover rounded-full">
<div class="w-6 h-6 rounded-full bg-blue-600 flex items-center justify-center text-white text-xs font-bold">{{ member.username|first|upper }}</div>
<span class="text-sm text-secondary">{{ member.username }}</span>
<span class="text-xs text-tertiary">({{ member.get_role_display }})</span>
</div>
{% endfor %}
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<a href="{% url 'issue_list' %}?project={{ project.pk }}" class="p-4 card-surface hover:border-blue-300 transition-colors">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-amber-100 flex items-center justify-center"><svg class="w-5 h-5 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9"/></svg></div>
<div><p class="font-semibold text-primary">Issues</p><p class="text-sm text-tertiary">{{ issues.count }} total</p></div>
</div>
</a>
<a href="{% url 'ticket_list' %}?project={{ project.pk }}" class="p-4 card-surface hover:border-blue-300 transition-colors">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-blue-100 flex items-center justify-center"><svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z"/></svg></div>
<div><p class="font-semibold text-primary">Tickets</p><p class="text-sm text-tertiary">{{ tickets.count }} recent</p></div>
</div>
</a>
<a href="{% url 'invoice_list' %}?project={{ project.pk }}" class="p-4 card-surface hover:border-blue-300 transition-colors">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center"><svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
<div><p class="font-semibold text-primary">Invoices</p><p class="text-sm text-tertiary">View finance</p></div>
</div>
</a>
<a href="{% url 'board_list' %}" class="p-4 card-surface hover:border-blue-300 transition-colors">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-sky-100 flex items-center justify-center"><svg class="w-5 h-5 text-sky-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7"/></svg></div>
<div><p class="font-semibold text-primary">Kanban</p><p class="text-sm text-tertiary">View boards</p></div>
</div>
</a>
</div>
{% if tickets %}
<div class="mt-6">
<h3 class="font-semibold text-primary mb-3">Recent Tickets</h3>
{% for ticket in tickets %}
<a href="{% url 'ticket_detail' ticket.pk %}" class="flex items-center justify-between p-3 rounded-xl hover:bg-hover transition-colors">
<span class="text-sm font-medium text-primary">{{ ticket.title }}</span>
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if ticket.ticket_type == 'bug' %}bg-red-50 text-red-700 border-red-200{% elif ticket.ticket_type == 'feature_request' %}bg-blue-50 text-blue-700 border-blue-200{% else %}bg-sky-50 text-sky-700 border-sky-200{% endif %}">{{ ticket.get_ticket_type_display }}</span>
</a>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% url 'project_delete' project.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-modal-project" delete_url=delete_url %}
{% endblock %}
@@ -1,22 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{% if object %}Edit{% else %}New{% endif %} Project{% endblock %}
{% block content_inner %}
<div class="max-w-2xl mx-auto">
<div class="card-surface p-6">
<h1 class="text-xl font-bold text-primary mb-1">{% if object %}Edit{% else %}New{% endif %} Project</h1>
<p class="text-sm text-tertiary mb-6">{% if object %}Update the project details below.{% else %}Fill in the details to create a new project.{% endif %}</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
{% if object %}Save Changes{% else %}Create Project{% endif %}
</button>
<a href="{% url 'project_list' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -1,69 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Projects{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Projects</h1>
<p class="text-tertiary mt-1">Manage and track all your projects.</p>
</div>
<a href="{% url 'project_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Project
</a>
</div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-6">
{% for s in status_counts %}
<div class="stat-card">
<p class="text-sm text-tertiary">{{ s.status|title }}</p>
<p class="text-2xl font-bold text-primary">{{ s.count }}</p>
</div>
{% endfor %}
</div>
<div class="grid gap-4">
{% for project in projects %}
<div class="card-surface hover:border-blue-300 transition-colors">
<div class="p-5">
<div class="flex items-start justify-between">
<div class="flex items-start gap-4">
<div class="w-10 h-10 rounded-xl bg-blue-600 flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
{{ project.name|first|upper }}
</div>
<div>
<a href="{% url 'project_detail' project.pk %}" class="text-lg font-semibold text-primary hover:text-blue-600 transition-colors">{{ project.name }}</a>
<div class="flex items-center gap-3 mt-1">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if project.status == 'active' %}bg-green-50 text-green-700 border-green-200{% elif project.status == 'planning' %}bg-blue-50 text-blue-700 border-blue-200{% elif project.status == 'review' %}bg-amber-50 text-amber-700 border-amber-200{% else %}bg-gray-50 text-gray-700 border-gray-200{% endif %}">
{{ project.get_status_display }}
</span>
<span class="text-xs text-tertiary">Client: {{ project.client.username }}</span>
</div>
</div>
</div>
<div class="flex gap-2">
<a href="{% url 'project_detail' project.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">View</a>
{% if user.is_admin %}
<a href="{% url 'project_edit' project.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
<label for="delete-project-{{ project.pk }}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
{% endif %}
</div>
</div>
</div>
</div>
{% empty %}
<div class="card-surface p-12 text-center">
<svg class="w-12 h-12 text-muted mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
<p class="text-tertiary">No projects yet.</p>
<a href="{% url 'project_create' %}" class="inline-block mt-3 px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Create your first project</a>
</div>
{% endfor %}
</div>
{% if user.is_admin %}
{% for project in projects %}
{% with project_id=project.pk|stringformat:"s" %}
{% url 'project_delete' project.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-project-"|add:project_id title="Delete Project" message="Are you sure you want to delete this project? This action cannot be undone." delete_url=delete_url %}
{% endwith %}
{% endfor %}
{% endif %}
{% endblock %}
@@ -1,20 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Delete Ticket{% endblock %}
{% block content_inner %}
<div class="max-w-lg mx-auto">
<div class="card-surface p-6 text-center">
<div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mx-auto mb-3">
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</div>
<h1 class="text-base font-semibold text-primary mb-2">Delete Ticket</h1>
<p class="text-sm text-tertiary mb-5">Are you sure you want to delete <strong>"{{ object.title }}"</strong>?</p>
<form method="post">
{% csrf_token %}
<div class="flex gap-2.5 justify-center">
<button type="submit" class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition-colors">Delete</button>
<a href="{% url 'ticket_detail' object.pk %}" class="px-4 py-2 rounded-lg border border-soft text-sm font-medium text-secondary hover:bg-hover transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -1,79 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{{ ticket.title }}{% endblock %}
{% block content_inner %}
<div class="max-w-4xl mx-auto">
<div class="card-surface overflow-hidden mb-6">
<div class="p-6">
<div class="flex items-start justify-between mb-4">
<div>
<div class="flex items-center gap-2 text-sm text-tertiary mb-1">
<a href="{% url 'ticket_list' %}" class="hover:text-blue-600">Tickets</a>
<span>/</span>
<span class="text-primary">{{ ticket.title|truncatechars:30 }}</span>
</div>
<h1 class="text-2xl font-bold text-primary">{{ ticket.title }}</h1>
</div>
<div class="flex gap-2">
<a href="{% url 'ticket_edit' ticket.pk %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Edit</a>
{% if user.is_admin %}
<label for="delete-modal-ticket" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
{% endif %}
</div>
</div>
<div class="flex flex-wrap gap-2 mb-4">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if ticket.ticket_type == 'bug' %}bg-red-50 text-red-700 border-red-200{% elif ticket.ticket_type == 'feature_request' %}bg-blue-50 text-blue-700 border-blue-200{% else %}bg-sky-50 text-sky-700 border-sky-200{% endif %}">
{{ ticket.get_ticket_type_display }}
</span>
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if ticket.status == 'new' %}bg-gray-50 text-gray-700 border-gray-200{% elif ticket.status == 'acknowledged' %}bg-blue-50 text-blue-700 border-blue-200{% elif ticket.status == 'in_progress' %}bg-amber-50 text-amber-700 border-amber-200{% elif ticket.status == 'done' %}bg-green-50 text-green-700 border-green-200{% else %}bg-red-50 text-red-700 border-red-200{% endif %}">
{{ ticket.get_status_display }}
</span>
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if ticket.priority == 'high' %}bg-red-50 text-red-700 border-red-200{% elif ticket.priority == 'medium' %}bg-amber-50 text-amber-700 border-amber-200{% else %}bg-blue-50 text-blue-700 border-blue-200{% endif %}">
{{ ticket.get_priority_display }}
</span>
</div>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 p-4 bg-hover rounded-xl mb-6">
<div><p class="text-xs text-muted uppercase font-medium">Creator</p><p class="text-sm font-semibold text-primary mt-0.5">{{ ticket.creator.username }}</p></div>
<div><p class="text-xs text-muted uppercase font-medium">Project</p><p class="text-sm font-semibold text-primary mt-0.5"><a href="{% url 'project_detail' ticket.project.pk %}" class="hover:text-blue-600">{{ ticket.project.name|default:"-" }}</a></p></div>
<div><p class="text-xs text-muted uppercase font-medium">Assignee</p><p class="text-sm font-semibold text-primary mt-0.5">{{ ticket.assignee.username|default:"Unassigned" }}</p></div>
</div>
<div class="prose prose-sm max-w-none">
<div class="text-secondary">{{ ticket.description|safe }}</div>
</div>
</div>
</div>
<div class="card-surface overflow-hidden">
<div class="px-6 py-4 border-b border-subtle">
<h2 class="font-semibold text-primary">Comments ({{ comments.count }})</h2>
</div>
<div class="p-6">
<div id="comment-list" class="space-y-4 mb-6">
{% for comment in comments %}
<div class="flex gap-3">
<div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white text-xs font-bold flex-shrink-0">{{ comment.author.username|first|upper }}</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-primary">{{ comment.author.username }}</span>
<span class="text-xs text-muted">{{ comment.created_at|timesince }} ago</span>
</div>
<p class="text-sm text-secondary">{{ comment.body }}</p>
</div>
</div>
{% empty %}
<p class="text-sm text-muted text-center py-4">No comments yet. Be the first to comment.</p>
{% endfor %}
</div>
<form method="post" action="{% url 'ticket_comment' ticket.pk %}"
hx-target="#comment-list" hx-swap="innerHTML"
class="border-t border-subtle pt-4">
{% csrf_token %}
<label class="text-sm font-medium text-secondary mb-2 block">Add a comment</label>
<textarea name="body" rows="2" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors resize-y" placeholder="Write your comment..." required></textarea>
<button type="submit" class="mt-2 px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Post Comment</button>
</form>
</div>
</div>
</div>
{% url 'ticket_delete' ticket.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-modal-ticket" delete_url=delete_url %}
{% endblock %}
-56
View File
@@ -1,56 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}{% if object %}Edit{% else %}New{% endif %} Ticket{% endblock %}
{% block content_inner %}
<div class="max-w-2xl mx-auto">
<div class="card-surface p-6">
<h1 class="text-xl font-bold text-primary mb-1">{% if object %}Edit{% else %}New{% endif %} Ticket</h1>
<p class="text-sm text-tertiary mb-6">{% if object %}Update the ticket.{% else %}Submit a new support ticket or feature request.{% endif %}</p>
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "partials/form_field.html" %}
{% endfor %}
<div class="flex gap-3 pt-2">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Save</button>
<a href="{% url 'ticket_list' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const descField = document.querySelector('textarea[name="description"]');
if (descField) {
const container = document.createElement('div');
container.style.height = '400px';
container.className = 'mb-4 rounded-xl';
descField.parentNode.insertBefore(container, descField);
descField.style.display = 'none';
const quill = new Quill(container, {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
[{ 'color': [] }, { 'background': [] }],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'align': [] }],
['link', 'image', 'video'],
['blockquote', 'code-block'],
['clean']
]
}
});
quill.root.innerHTML = descField.value;
document.querySelector('form').addEventListener('submit', function() {
descField.value = quill.root.innerHTML;
});
}
});
</script>
{% endblock %}
-72
View File
@@ -1,72 +0,0 @@
{% extends "base_authenticated.html" %}
{% block title %}Tickets{% endblock %}
{% block content_inner %}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-primary">Tickets</h1>
<p class="text-tertiary mt-1">Client support and feature requests.</p>
</div>
<a href="{% url 'ticket_create' %}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">
<svg class="w-4 h-4 mr-1 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
New Ticket
</a>
</div>
<div class="card-surface overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-semibold text-muted uppercase tracking-wider bg-hover">
<th class="px-4 py-3 text-left">Title</th>
<th class="px-4 py-3 text-left">Type</th>
<th class="px-4 py-3 text-left">Status</th>
<th class="px-4 py-3 text-left">Priority</th>
<th class="px-4 py-3 text-left">Project</th>
<th class="px-4 py-3 text-left">Assignee</th>
<th class="px-4 py-3 text-left">Created</th>
{% if user.is_admin %}<th class="px-4 py-3 text-left">Actions</th>{% endif %}
</tr>
</thead>
<tbody>
{% for ticket in tickets %}
<tr class="hover:bg-hover transition-colors border-t border-subtle">
<td class="px-4 py-3"><a href="{% url 'ticket_detail' ticket.pk %}" class="font-medium text-primary hover:text-blue-600">{{ ticket.title }}</a></td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if ticket.ticket_type == 'bug' %}bg-red-50 text-red-700 border-red-200{% elif ticket.ticket_type == 'feature_request' %}bg-blue-50 text-blue-700 border-blue-200{% else %}bg-sky-50 text-sky-700 border-sky-200{% endif %}">
{{ ticket.get_ticket_type_display }}
</span>
</td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if ticket.status == 'new' %}bg-gray-50 text-gray-700 border-gray-200{% elif ticket.status == 'acknowledged' %}bg-blue-50 text-blue-700 border-blue-200{% elif ticket.status == 'in_progress' %}bg-amber-50 text-amber-700 border-amber-200{% elif ticket.status == 'done' %}bg-green-50 text-green-700 border-green-200{% else %}bg-red-50 text-red-700 border-red-200{% endif %}">
{{ ticket.get_status_display }}
</span>
</td>
<td class="px-4 py-3">
<span class="text-xs px-2 py-1 rounded-full font-medium border {% if ticket.priority == 'high' %}bg-red-50 text-red-700 border-red-200{% elif ticket.priority == 'medium' %}bg-amber-50 text-amber-700 border-amber-200{% else %}bg-blue-50 text-blue-700 border-blue-200{% endif %}">
{{ ticket.get_priority_display }}
</span>
</td>
<td class="px-4 py-3 text-secondary">{{ ticket.project.name|default:"-" }}</td>
<td class="px-4 py-3 text-secondary">{{ ticket.assignee.username|default:"-" }}</td>
<td class="px-4 py-3 text-tertiary">{{ ticket.created_at|date:"M d" }}</td>
{% if user.is_admin %}
<td class="px-4 py-3">
<label for="delete-ticket-{{ ticket.pk }}" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-red-50 text-red-700 hover:bg-red-100 border border-red-200 transition-colors cursor-pointer">Delete</label>
</td>
{% endif %}
</tr>
{% empty %}
<tr><td colspan="{% if user.is_admin %}8{% else %}7{% endif %}" class="text-center py-12 text-tertiary">No tickets found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if user.is_admin %}
{% for ticket in tickets %}
{% with ticket_id=ticket.pk|stringformat:"s" %}
{% url 'ticket_delete' ticket.pk as delete_url %}
{% include "components/delete_modal.html" with modal_id="delete-ticket-"|add:ticket_id title="Delete Ticket" message="Are you sure you want to delete this ticket? This action cannot be undone." delete_url=delete_url %}
{% endwith %}
{% endfor %}
{% endif %}
{% endblock %}
-24
View File
@@ -1,24 +0,0 @@
{% load form_helpers %}
<div class="space-y-1.5">
<label class="text-sm font-medium text-secondary">{{ field.label }}</label>
{% with wt=field|field_type %}
{% if wt == "CheckboxInput" or wt == "CheckboxSelectMultiple" %}
<label class="flex items-center gap-3 cursor-pointer">
{{ field }}
<span class="text-sm text-secondary">{{ field.label }}</span>
</label>
{% elif wt == "Select" or wt == "NullBooleanSelect" or wt == "SelectMultiple" %}
{{ field|add_class:"w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors" }}
{% elif wt == "Textarea" %}
{{ field|add_class:"w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors resize-y" }}
{% else %}
{{ field|add_class:"w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors" }}
{% endif %}
{% endwith %}
{% if field.errors %}
<p class="text-xs text-red-600">{{ field.errors.0 }}</p>
{% endif %}
{% if field.help_text %}
<p class="text-xs text-tertiary">{{ field.help_text }}</p>
{% endif %}
</div>
@@ -1,8 +0,0 @@
{% for comment in comments %}
<div class="border-b border-base-300 py-2">
<p class="text-sm"><strong>{{ comment.author.username }}</strong> <span class="text-xs opacity-50">{{ comment.created_at|timesince }} ago</span></p>
<p>{{ comment.body }}</p>
</div>
{% empty %}
<p class="text-sm opacity-50">No comments yet.</p>
{% endfor %}
-41
View File
@@ -1,41 +0,0 @@
<div class="card-surface hover:border-blue-300 transition-all cursor-grab active:cursor-grabbing"
id="card-{{ card.pk }}"
data-card-id="{{ card.pk }}">
<div class="p-3">
<div class="flex items-start justify-between gap-2">
<div class="flex-1 min-w-0">
<a href="{% url 'card_detail' card.pk %}"
class="text-sm font-medium text-primary leading-snug hover:text-blue-600 block truncate"
onclick="event.stopPropagation()">
{{ card.title }}
</a>
<div class="flex items-center gap-2 mt-1.5">
{% if card.priority == 'urgent' %}
<span class="text-[10px] px-1.5 py-0.5 rounded font-semibold border bg-red-50 text-red-700 border-red-200">Urgent</span>
{% elif card.priority == 'high' %}
<span class="text-[10px] px-1.5 py-0.5 rounded font-semibold border bg-amber-50 text-amber-700 border-amber-200">High</span>
{% elif card.priority == 'medium' %}
<span class="text-[10px] px-1.5 py-0.5 rounded font-semibold border bg-blue-50 text-blue-700 border-blue-200">Med</span>
{% elif card.priority == 'low' %}
<span class="text-[10px] px-1.5 py-0.5 rounded font-semibold border bg-gray-50 text-gray-600 border-gray-200">Low</span>
{% endif %}
{% if card.assignee %}
<span class="text-[10px] text-muted truncate">{{ card.assignee.username }}</span>
{% endif %}
</div>
</div>
<div class="flex gap-1 flex-shrink-0 no-drag">
<a href="{% url 'card_edit' card.pk %}"
class="text-muted hover:text-blue-500 transition-colors"
onclick="event.stopPropagation()">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
</a>
<button type="button"
class="text-muted hover:text-red-500 transition-colors"
onclick="event.stopPropagation(); openCardDeleteModal('{% url 'card_delete' card.pk %}')">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</button>
</div>
</div>
</div>
</div>
-43
View File
@@ -1,43 +0,0 @@
{% load form_helpers %}
<div class="card-surface" id="comment-{{ comment.pk }}">
<div class="p-4">
<div class="flex items-start gap-3">
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-xs font-bold flex-shrink-0">{{ comment.author.username|first|upper }}</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-primary">{{ comment.author.username }}</span>
<span class="text-xs text-muted">{{ comment.created_at|timesince }} ago</span>
</div>
<div class="prose prose-sm max-w-none text-secondary mb-2">
{{ comment.body|render_markdown }}
</div>
{% if not comment.parent %}
<button type="button"
class="text-xs text-blue-600 hover:text-blue-800 font-medium"
onclick="event.stopPropagation(); document.getElementById('reply-form-{{ comment.pk }}').classList.toggle('hidden')">
Reply
</button>
<form method="post" action="{% url 'card_comment' comment.card.pk %}"
id="reply-form-{{ comment.pk }}"
class="hidden mt-3 space-y-2"
hx-target="#comment-{{ comment.pk }}" hx-swap="afterend">
{% csrf_token %}
<input type="hidden" name="parent" value="{{ comment.pk }}">
<textarea name="body" class="w-full px-3 py-2 rounded-lg border border-soft bg-white text-sm text-primary focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 transition-colors resize-y min-h-[80px]" placeholder="Write a reply..." required></textarea>
<div class="flex gap-2">
<button type="submit" class="px-3 py-1.5 rounded-lg text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 transition-colors">Reply</button>
<button type="button" class="px-3 py-1.5 rounded-lg text-sm font-medium text-secondary hover:bg-hover border border-soft transition-colors" onclick="this.closest('form').classList.add('hidden')">Cancel</button>
</div>
</form>
{% endif %}
{% if comment.replies.all %}
<div class="mt-3 space-y-3 pl-4 border-l-2 border-soft">
{% for reply in comment.replies.all %}
{% include "partials/kanban/comment.html" with comment=reply %}
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
@@ -1,14 +0,0 @@
{% for comment in comments %}
<div class="flex gap-3">
<div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white text-xs font-bold flex-shrink-0">{{ comment.author.username|first|upper }}</div>
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-semibold text-gray-900">{{ comment.author.username }}</span>
<span class="text-xs text-gray-400">{{ comment.created_at|timesince }} ago</span>
</div>
<p class="text-sm text-gray-700">{{ comment.body }}</p>
</div>
</div>
{% empty %}
<p class="text-sm text-gray-400 text-center py-4">No comments yet.</p>
{% endfor %}