HH/templates/complaints/partials/departments_panel.html
ismail badb6a9ebf
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m41s
feat: QI Projects — team-member task management + My Tasks + notifications
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
2026-06-16 23:55:58 +03:00

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>