All checks were successful
Build and Push Docker Image / build (push) Successful in 2m41s
Fixed 3 gaps in the QI Projects module: Gap 1 (critical): team members couldn't manage their own tasks — the toggle checkbox/edit/delete were gated behind admin-only can_edit. Now: - _can_manage_task() helper: admins OR the task assignee OR project team members - task_toggle_status + htmx_task_toggle_status use the new helper - task_row.html shows the toggle for assignees (task.assigned_to.user_id check) Gap 2: no "My QI Tasks" view — added /projects/my-tasks/ showing tasks assigned to the current user across all projects, with toggle links + stats. Sidebar link. Gap 3: no notification on task assignment — added apps/projects/signals.py (post_save on QIProjectTask → create_in_app_notification). apps.py ready() wired. Tested (headed, 11 PASS / 1 FAIL): - Cross-department team members (Contact Center + different dept) can VIEW the project AND toggle their assigned tasks (both PASS) - My Tasks view loads for team members - Excel export returns 500 (real bug, reported) - Project close via edit form needs correct hospital UUID (test harness issue) Also bundles accumulated in-progress work across complaints, observations, organizations, templates, and other modules. Harness: seed_e2e_project + get_e2e_project_state CLI + qi-projects-workflow.spec.ts
123 lines
8.2 KiB
HTML
123 lines
8.2 KiB
HTML
{% load i18n %}
|
|
<section class="bg-white rounded-2xl p-4 shadow-sm border border-slate-100">
|
|
<div class="flex justify-between items-center mb-3">
|
|
<h3 class="text-sm font-bold text-navy uppercase tracking-wide">{% trans "Involved Departments" %}</h3>
|
|
{% if can_manage_actions and complaint.is_active_status %}
|
|
<button type="button" onclick="showAddDepartmentModal()"
|
|
class="px-2.5 py-1 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-semibold hover:opacity-90 transition flex items-center gap-1">
|
|
<i data-lucide="plus" class="w-3.5 h-3.5"></i> {% trans "Add" %}
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if can_manage_actions and complaint.is_active_status and ai_department_suggested %}
|
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-3 mb-3 flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<i data-lucide="sparkles" class="w-4 h-4 text-blue-600"></i>
|
|
<span class="text-xs font-semibold text-navy">{{ complaint.department.name }}</span>
|
|
<span class="px-1.5 py-0.5 bg-blue-100 text-blue-700 rounded text-[10px] font-bold">{% trans "AI" %}</span>
|
|
</div>
|
|
<form method="post" action="{% url 'complaints:confirm_ai_department_suggestion' complaint_pk=complaint.pk %}" class="inline">
|
|
{% csrf_token %}
|
|
<button type="submit" class="px-2 py-1 text-xs bg-navy text-white rounded-lg font-semibold hover:bg-blue transition">
|
|
{% trans "Confirm" %}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if complaint.involved_departments.exists %}
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b border-slate-100">
|
|
<th class="text-left py-2 pr-2 text-[10px] font-bold text-slate/50 uppercase">{% trans "Department" %}</th>
|
|
<th class="text-left py-2 px-2 text-[10px] font-bold text-slate/50 uppercase">{% trans "Role" %}</th>
|
|
<th class="text-left py-2 px-2 text-[10px] font-bold text-slate/50 uppercase">{% trans "Status" %}</th>
|
|
{% if can_manage_actions and complaint.is_active_status %}
|
|
<th class="text-right py-2 pl-2 text-[10px] font-bold text-slate/50 uppercase"></th>
|
|
{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-50">
|
|
{% for dept in complaint.involved_departments.all %}
|
|
<tr class="hover:bg-slate-50/50 transition {% if dept.is_primary %}bg-navy/5{% endif %}">
|
|
<td class="py-2.5 pr-2">
|
|
<div class="flex items-center gap-1.5">
|
|
{% if dept.is_primary %}
|
|
<span class="w-1.5 h-1.5 rounded-full bg-navy shrink-0"></span>
|
|
{% endif %}
|
|
<span class="font-semibold text-navy text-xs">{{ dept.department.name }}</span>
|
|
</div>
|
|
{% if dept.assigned_to %}
|
|
<p class="text-[10px] text-slate mt-0.5">
|
|
<i data-lucide="user-check" class="w-3 h-3 inline"></i> {{ dept.assigned_to.get_full_name }}
|
|
</p>
|
|
{% endif %}
|
|
{% if dept.response_notes %}
|
|
<p class="text-[10px] text-slate/70 mt-1 italic max-w-xs truncate">{{ dept.response_notes|truncatechars:60 }}</p>
|
|
{% endif %}
|
|
</td>
|
|
<td class="py-2.5 px-2">
|
|
<span class="px-1.5 py-0.5 bg-light text-navy rounded text-[10px] font-semibold">{{ dept.get_role_display }}</span>
|
|
</td>
|
|
<td class="py-2.5 px-2">
|
|
{% if not dept.response_submitted %}
|
|
<span class="text-[10px] text-slate/50 italic">{% trans "No response" %}</span>
|
|
{% elif dept.manager_review_status == 'rejected' %}
|
|
<span class="inline-flex items-center gap-1 text-[10px] font-bold text-red-600"><i data-lucide="x-circle" class="w-3 h-3"></i> {% trans "Rejected" %}</span>
|
|
{% elif dept.manager_review_status == 'pending' %}
|
|
<span class="inline-flex items-center gap-1 text-[10px] font-bold text-amber-600"><i data-lucide="clock" class="w-3 h-3"></i> {% trans "Pending Review" %}</span>
|
|
{% elif dept.acceptance_status == 'acceptable' %}
|
|
<span class="inline-flex items-center gap-1 text-[10px] font-bold text-green-600"><i data-lucide="check-circle" class="w-3 h-3"></i> {% trans "Accepted" %}</span>
|
|
{% elif dept.acceptance_status == 'not_acceptable' %}
|
|
<span class="inline-flex items-center gap-1 text-[10px] font-bold text-red-600"><i data-lucide="x-circle" class="w-3 h-3"></i> {% trans "Rejected" %}</span>
|
|
{% elif dept.acceptance_status == 'pending' and dept.manager_review_status == 'approved' %}
|
|
<span class="inline-flex items-center gap-1 text-[10px] font-bold text-green-600"><i data-lucide="check-circle" class="w-3 h-3"></i> {% trans "Approved" %}</span>
|
|
{% else %}
|
|
<span class="inline-flex items-center gap-1 text-[10px] font-bold text-amber-600"><i data-lucide="clock" class="w-3 h-3"></i> {% trans "Pending" %}</span>
|
|
{% endif %}
|
|
</td>
|
|
{% if can_manage_actions and complaint.is_active_status %}
|
|
<td class="py-2.5 pl-2 text-right">
|
|
<div class="flex items-center justify-end gap-1">
|
|
{% if not dept.response_submitted %}
|
|
<button type="button"
|
|
onclick="openDeptResponseModal('complaint', '{{ dept.pk }}', '{% url 'complaints:involved_department_response' pk=dept.pk %}', '{{ complaint.reference_number }}', '{{ dept.department.name|escapejs }}')"
|
|
class="p-1.5 text-slate hover:text-blue transition" title="{% trans 'Submit Response' %}">
|
|
<i data-lucide="message-square" class="w-4 h-4"></i>
|
|
</button>
|
|
{% endif %}
|
|
<a href="{% url 'complaints:involved_department_edit' pk=dept.pk %}"
|
|
class="p-1.5 text-slate hover:text-navy transition" title="{% trans 'Edit' %}">
|
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
|
</a>
|
|
<form method="post" action="{% url 'complaints:involved_department_remove' pk=dept.pk %}"
|
|
class="inline" onsubmit="return confirm('{% trans "Are you sure you want to remove this department?" %}')">
|
|
{% csrf_token %}
|
|
<button type="submit" class="p-1.5 text-slate hover:text-red-500 transition" title="{% trans 'Remove' %}">
|
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<i data-lucide="building-2" class="w-10 h-10 mx-auto text-slate-300 mb-2"></i>
|
|
<p class="text-slate text-sm mb-3">{% trans "No departments involved yet" %}</p>
|
|
{% if can_manage_actions and complaint.is_active_status %}
|
|
<button type="button" onclick="showAddDepartmentModal()"
|
|
class="px-3 py-1.5 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-semibold hover:opacity-90 transition inline-flex items-center gap-2">
|
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add First Department" %}
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</section>
|