556 lines
31 KiB
HTML
556 lines
31 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% trans "Action" %} #{{ action.id|slice:":8" }} - PX360{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.tab-active {
|
|
border-bottom: 3px solid #005696;
|
|
color: #005696;
|
|
font-weight: 700;
|
|
}
|
|
.tab-inactive {
|
|
color: #64748b;
|
|
font-weight: 500;
|
|
}
|
|
.tab-inactive:hover { color: #005696; }
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 30px;
|
|
}
|
|
.timeline::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 8px;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background: #e2e8f0;
|
|
}
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-bottom: 24px;
|
|
}
|
|
.timeline-item::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -26px;
|
|
top: 5px;
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
background: white;
|
|
border: 3px solid #f57c00;
|
|
z-index: 1;
|
|
}
|
|
.timeline-item.status_change::before { border-color: #005696; }
|
|
.timeline-item.assignment::before { border-color: #388e3c; }
|
|
.timeline-item.escalation::before { border-color: #d32f2f; }
|
|
.timeline-item.approval::before { border-color: #4caf50; }
|
|
.timeline-item.evidence::before { border-color: #f57c00; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
|
|
|
|
<!-- Breadcrumb & Header -->
|
|
<header class="mb-6">
|
|
<div class="flex items-center gap-2 text-sm text-slate mb-2 flex-wrap">
|
|
<a href="{% url 'actions:action_list' %}" class="hover:text-navy font-medium">{% trans "Actions" %}</a>
|
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
|
<span class="font-bold text-navy">{{ action.id|slice:":8" }}</span>
|
|
<span class="ml-1 px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider
|
|
{% if action.status == 'completed' %}bg-green-100 text-green-700
|
|
{% elif action.status == 'in_progress' %}bg-blue-100 text-blue-700
|
|
{% elif action.status == 'cancelled' %}bg-slate-100 text-slate-600
|
|
{% elif action.status == 'pending_approval' %}bg-purple-100 text-purple-700
|
|
{% else %}bg-yellow-100 text-yellow-700{% endif %}">
|
|
{{ action.get_status_display }}
|
|
</span>
|
|
{% if action.is_overdue %}
|
|
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase bg-red-100 text-red-700">
|
|
{% trans "Overdue" %}
|
|
</span>
|
|
{% endif %}
|
|
{% if action.escalation_level > 0 %}
|
|
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase bg-red-500 text-white">
|
|
Level {{ action.escalation_level }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<h1 class="text-2xl font-bold text-navy mb-2">{{ action.title }}</h1>
|
|
<div class="flex flex-wrap items-center gap-4 text-sm text-slate">
|
|
{% if action.department %}
|
|
<span class="flex items-center gap-1">
|
|
<i data-lucide="building-2" class="w-4 h-4"></i>
|
|
{{ action.department.get_localized_name }}
|
|
</span>
|
|
{% endif %}
|
|
<span class="flex items-center gap-1">
|
|
<i data-lucide="calendar" class="w-4 h-4"></i>
|
|
{{ action.created_at|date:"d M Y" }}
|
|
</span>
|
|
<span class="flex items-center gap-1 {% if action.is_overdue %}text-red-600 font-bold{% endif %}">
|
|
<i data-lucide="clock" class="w-4 h-4"></i>
|
|
{% trans "Due" %} {{ action.due_at|date:"d M Y, h:i A" }}
|
|
</span>
|
|
{% if action.assigned_to %}
|
|
<span class="flex items-center gap-1">
|
|
<i data-lucide="user-check" class="w-4 h-4"></i>
|
|
{{ action.assigned_to.get_full_name }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<!-- SLA Progress -->
|
|
<div class="mt-4 flex items-center gap-3">
|
|
<div class="flex-1 max-w-xs h-2 bg-slate-200 rounded-full overflow-hidden">
|
|
<div class="h-full rounded-full {% if action.is_overdue %}bg-red-500{% elif sla_progress > 80 %}bg-orange-500{% else %}bg-green-500{% endif %}" style="width: {{ sla_progress }}%"></div>
|
|
</div>
|
|
<span class="text-xs font-bold {% if action.is_overdue %}text-red-600{% else %}text-slate{% endif %}">{{ sla_progress }}%</span>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Tab Navigation -->
|
|
<nav class="bg-white px-6 flex gap-6 border-b shadow-sm mb-6 rounded-t-2xl">
|
|
<button class="py-4 text-sm tab-active" onclick="switchTab('details')" id="tab-details">
|
|
{% trans "Details" %}
|
|
</button>
|
|
<button class="py-4 text-sm tab-inactive" onclick="switchTab('activity')" id="tab-activity">
|
|
{% trans "Activity" %} ({{ logs.count }})
|
|
</button>
|
|
<button class="py-4 text-sm tab-inactive" onclick="switchTab('evidence')" id="tab-evidence">
|
|
{% trans "Evidence" %} ({{ evidence_attachments.count }})
|
|
</button>
|
|
<button class="py-4 text-sm tab-inactive" onclick="switchTab('attachments')" id="tab-attachments">
|
|
{% trans "Attachments" %} ({{ attachments.count }})
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Main Content Grid -->
|
|
<main class="grid grid-cols-12 gap-6">
|
|
|
|
<!-- Left Column -->
|
|
<div class="col-span-8 space-y-6">
|
|
|
|
<!-- Details Tab -->
|
|
<div id="panel-details" class="tab-panel space-y-6">
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<div class="grid grid-cols-2 sm:grid-cols-3 gap-4 py-2">
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate-400 uppercase">{% trans "Category" %}</p>
|
|
<p class="text-sm font-bold text-navy mt-1">{{ action.get_category_display }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate-400 uppercase">{% trans "Priority" %}</p>
|
|
<p class="text-sm font-bold mt-1
|
|
{% if action.priority == 'high' %}text-red-600
|
|
{% elif action.priority == 'medium' %}text-orange-600
|
|
{% else %}text-green-600{% endif %}">{{ action.get_priority_display }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate-400 uppercase">{% trans "Severity" %}</p>
|
|
<p class="text-sm font-bold text-navy mt-1">{{ action.get_severity_display }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate-400 uppercase">{% trans "Source" %}</p>
|
|
<p class="text-sm font-bold text-navy mt-1">{{ action.get_source_type_display }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate-400 uppercase">{% trans "Created" %}</p>
|
|
<p class="text-sm font-bold text-navy mt-1">{{ action.created_at|date:"d M Y, h:i A" }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate-400 uppercase">{% trans "Requires Approval" %}</p>
|
|
<p class="text-sm font-bold mt-1 {% if action.requires_approval %}text-orange-600{% else %}text-slate{% endif %}">
|
|
{% if action.requires_approval %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{% if action.description %}
|
|
<div class="py-3 border-t border-slate-100">
|
|
<p class="text-xs font-semibold text-slate-400 uppercase mb-2">{% trans "Description" %}</p>
|
|
<div class="bg-slate-50 p-4 rounded-xl border-l-4 border-navy">
|
|
<p class="text-sm leading-relaxed text-slate-700">{{ action.description|linebreaksbr }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if action.action_plan %}
|
|
<div class="py-3 border-t border-slate-100">
|
|
<p class="text-xs font-semibold text-slate-400 uppercase mb-2">{% trans "Action Plan" %}</p>
|
|
<div class="bg-blue-50 p-4 rounded-xl border border-blue-100">
|
|
<p class="text-sm leading-relaxed text-slate-700">{{ action.action_plan|linebreaksbr }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if action.outcome %}
|
|
<div class="py-3 border-t border-slate-100">
|
|
<p class="text-xs font-semibold text-slate-400 uppercase mb-2">{% trans "Outcome" %}</p>
|
|
<div class="bg-green-50 p-4 rounded-xl border border-green-100">
|
|
<p class="text-sm leading-relaxed text-slate-700">{{ action.outcome|linebreaksbr }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if action.content_type.model == 'complaint' and action.source_object %}
|
|
<div class="py-3 border-t border-slate-100">
|
|
<p class="text-xs font-semibold text-slate-400 uppercase mb-2">{% trans "Related Complaint" %}</p>
|
|
<a href="{% url 'complaints:complaint_detail' pk=action.source_object.pk %}" class="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 border border-blue-200 rounded-xl hover:bg-blue-100 transition">
|
|
<i data-lucide="link" class="w-4 h-4 text-blue-600"></i>
|
|
<div>
|
|
<p class="text-sm font-bold text-navy">{{ action.source_object.reference_number }}</p>
|
|
<p class="text-xs text-slate-500">{{ action.source_object.title|truncatechars:60 }}</p>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Activity Tab -->
|
|
<div id="panel-activity" class="tab-panel hidden">
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4">{% trans "Activity Log" %}</h3>
|
|
{% if logs %}
|
|
<div class="timeline">
|
|
{% for log in logs %}
|
|
<div class="timeline-item {{ log.log_type }}">
|
|
<div class="bg-slate-50 rounded-xl p-4">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<span class="font-bold text-navy text-sm">{{ log.message }}</span>
|
|
<span class="text-xs text-slate-400">{{ log.created_at|date:"M d, Y H:i" }}</span>
|
|
</div>
|
|
{% if log.created_by %}
|
|
<div class="text-xs text-slate-500">
|
|
<i data-lucide="user" class="w-3 h-3 inline"></i> {{ log.created_by.get_full_name }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<i data-lucide="history" class="w-12 h-12 mx-auto text-slate-300 mb-3"></i>
|
|
<p class="text-slate text-sm">{% trans "No activity yet" %}</p>
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Evidence Tab -->
|
|
<div id="panel-evidence" class="tab-panel hidden">
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4">{% trans "Evidence Files" %}</h3>
|
|
|
|
{% if can_edit and action.status != 'closed' %}
|
|
<form method="post" action="{% url 'actions:action_upload_attachment' action.pk %}" enctype="multipart/form-data" class="mb-6 p-4 border-2 border-dashed border-slate-200 rounded-xl">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="is_evidence" value="on">
|
|
<div class="flex flex-wrap items-end gap-3">
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1">{% trans "File" %}</label>
|
|
<input type="file" name="file" required class="block w-full text-sm text-slate-500 border border-slate-200 rounded-xl cursor-pointer">
|
|
</div>
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1">{% trans "Description (optional)" %}</label>
|
|
<input type="text" name="description" class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm" placeholder="{% trans 'Brief description...' %}">
|
|
</div>
|
|
<button type="submit" class="px-4 py-2 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition text-sm flex items-center gap-2">
|
|
<i data-lucide="upload" class="w-4 h-4"></i> {% trans "Upload Evidence" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% endif %}
|
|
|
|
{% if evidence_attachments %}
|
|
<div class="space-y-3">
|
|
{% for evidence in evidence_attachments %}
|
|
<div class="flex items-center justify-between p-4 bg-orange-50 border-l-4 border-orange-400 rounded-r-xl">
|
|
<div class="flex items-center gap-3">
|
|
<i data-lucide="file-check" class="w-5 h-5 text-orange-500"></i>
|
|
<div>
|
|
<div class="font-semibold text-slate-800">{{ evidence.filename }}</div>
|
|
<div class="text-xs text-slate-500">
|
|
{% trans "Uploaded by" %} {{ evidence.uploaded_by.get_full_name }}
|
|
{% trans "on" %} {{ evidence.created_at|date:"M d, Y H:i" }}
|
|
({{ evidence.file_size|filesizeformat }})
|
|
</div>
|
|
{% if evidence.description %}
|
|
<div class="text-sm text-slate-600 mt-1">{{ evidence.description }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<a href="{{ evidence.file.url }}" target="_blank" class="p-2 rounded-lg bg-navy text-white hover:bg-blue transition">
|
|
<i data-lucide="download" class="w-4 h-4"></i>
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<i data-lucide="file-check" class="w-12 h-12 mx-auto text-slate-300 mb-3"></i>
|
|
<p class="text-slate text-sm">{% trans "No evidence files uploaded yet" %}</p>
|
|
{% if action.requires_approval and action.status != 'closed' %}
|
|
<p class="text-orange-500 text-sm mt-1">{% trans "Evidence is required before requesting approval" %}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Attachments Tab -->
|
|
<div id="panel-attachments" class="tab-panel hidden">
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4">{% trans "All Attachments" %}</h3>
|
|
|
|
{% if can_edit and action.status != 'closed' %}
|
|
<form method="post" action="{% url 'actions:action_upload_attachment' action.pk %}" enctype="multipart/form-data" class="mb-6 p-4 border-2 border-dashed border-slate-200 rounded-xl">
|
|
{% csrf_token %}
|
|
<div class="flex flex-wrap items-end gap-3">
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1">{% trans "File" %}</label>
|
|
<input type="file" name="file" required class="block w-full text-sm text-slate-500 border border-slate-200 rounded-xl cursor-pointer">
|
|
</div>
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label class="block text-xs font-semibold text-slate-500 uppercase mb-1">{% trans "Description (optional)" %}</label>
|
|
<input type="text" name="description" class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm" placeholder="{% trans 'Brief description...' %}">
|
|
</div>
|
|
<label class="flex items-center gap-2 text-sm text-slate-600">
|
|
<input type="checkbox" name="is_evidence" class="w-4 h-4 accent-navy">
|
|
{% trans "Mark as evidence" %}
|
|
</label>
|
|
<button type="submit" class="px-4 py-2 bg-navy text-white rounded-xl font-semibold hover:bg-blue transition text-sm flex items-center gap-2">
|
|
<i data-lucide="upload" class="w-4 h-4"></i> {% trans "Upload" %}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{% endif %}
|
|
|
|
{% if attachments %}
|
|
<div class="space-y-3">
|
|
{% for attachment in attachments %}
|
|
<div class="flex items-center justify-between p-4 bg-slate-50 border border-slate-100 rounded-xl">
|
|
<div class="flex items-center gap-3">
|
|
<i data-lucide="file" class="w-5 h-5 text-slate"></i>
|
|
<div>
|
|
<div class="font-semibold text-slate-800">
|
|
{{ attachment.filename }}
|
|
{% if attachment.is_evidence %}
|
|
<span class="ml-2 px-2 py-0.5 rounded text-xs font-medium bg-orange-100 text-orange-600">{% trans "Evidence" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="text-xs text-slate-500">
|
|
{% trans "Uploaded by" %} {{ attachment.uploaded_by.get_full_name }}
|
|
{% trans "on" %} {{ attachment.created_at|date:"M d, Y H:i" }}
|
|
({{ attachment.file_size|filesizeformat }})
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a href="{{ attachment.file.url }}" target="_blank" class="p-2 rounded-lg bg-navy text-white hover:bg-blue transition">
|
|
<i data-lucide="download" class="w-4 h-4"></i>
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-8">
|
|
<i data-lucide="paperclip" class="w-12 h-12 mx-auto text-slate-300 mb-3"></i>
|
|
<p class="text-slate text-sm">{% trans "No attachments" %}</p>
|
|
</div>
|
|
{% endif %}
|
|
</section>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Right Column (Sidebar) -->
|
|
<div class="col-span-4 space-y-6">
|
|
|
|
<!-- Approval Required -->
|
|
{% if action.status == 'pending_approval' and can_approve %}
|
|
<section class="bg-green-50 border border-green-200 rounded-2xl p-6">
|
|
<h3 class="font-bold text-navy mb-3 flex items-center gap-2">
|
|
<i data-lucide="check-circle" class="w-5 h-5 text-green-600"></i> {% trans "Approval Required" %}
|
|
</h3>
|
|
<p class="text-sm text-slate mb-3">{% trans "This action is awaiting your approval." %}</p>
|
|
<form method="post" action="{% url 'actions:action_approve' action.id %}">
|
|
{% csrf_token %}
|
|
<button type="submit" class="w-full bg-green-500 text-white px-4 py-2.5 rounded-xl font-semibold hover:bg-green-600 transition flex items-center justify-center gap-2">
|
|
<i data-lucide="check-circle" class="w-4 h-4"></i> {% trans "Approve Action" %}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<!-- Quick Actions -->
|
|
{% if can_edit %}
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="zap" class="w-4 h-4"></i> {% trans "Quick Actions" %}
|
|
</h3>
|
|
<div class="space-y-4">
|
|
<!-- Assign -->
|
|
<form method="post" action="{% url 'actions:action_assign' action.id %}">
|
|
{% csrf_token %}
|
|
<label class="block text-xs font-semibold text-slate-400 uppercase mb-2">{% trans "Assign To" %}</label>
|
|
<div class="flex gap-2">
|
|
<select name="user_id" class="flex-1 px-3 py-2 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-navy/20 outline-none" required>
|
|
<option value="">{% trans "Select user..." %}</option>
|
|
{% for user_obj in assignable_users %}
|
|
<option value="{{ user_obj.id }}" {% if action.assigned_to and action.assigned_to.id == user_obj.id %}selected{% endif %}>{{ user_obj.get_full_name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="submit" class="px-3 py-2 bg-navy text-white rounded-xl hover:bg-blue transition"><i data-lucide="user-check" class="w-4 h-4"></i></button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Change Status -->
|
|
<form method="post" action="{% url 'actions:action_change_status' action.id %}">
|
|
{% csrf_token %}
|
|
<label class="block text-xs font-semibold text-slate-400 uppercase mb-2">{% trans "Change Status" %}</label>
|
|
<select name="status" class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm mb-2 focus:ring-2 focus:ring-navy/20 outline-none" required>
|
|
{% for value, label in status_choices %}
|
|
<option value="{{ value }}" {% if action.status == value %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<textarea name="note" class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm mb-2 focus:ring-2 focus:ring-navy/20 outline-none" rows="2" placeholder="{% trans 'Optional note...' %}"></textarea>
|
|
<button type="submit" class="w-full bg-navy text-white px-4 py-2.5 rounded-xl font-semibold hover:bg-blue transition flex items-center justify-center gap-2 text-sm">
|
|
<i data-lucide="refresh-cw" class="w-4 h-4"></i> {% trans "Update Status" %}
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Escalate -->
|
|
<button type="button" onclick="document.getElementById('escalateModal').style.display='flex'" class="w-full bg-red-50 border border-red-200 text-red-600 px-4 py-2.5 rounded-xl font-semibold hover:bg-red-100 transition flex items-center justify-center gap-2 text-sm">
|
|
<i data-lucide="arrow-up-circle" class="w-4 h-4"></i> {% trans "Escalate" %}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<!-- Assignment Info -->
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="info" class="w-4 h-4"></i> {% trans "Assignment Info" %}
|
|
</h3>
|
|
<div class="space-y-3 text-sm">
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate-400 uppercase mb-1">{% trans "Assigned To" %}</p>
|
|
{% if action.assigned_to %}
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-7 h-7 rounded-full bg-navy text-white text-[10px] font-bold flex items-center justify-center">{{ action.assigned_to.first_name|first }}{{ action.assigned_to.last_name|first }}</span>
|
|
<div>
|
|
<p class="font-bold text-navy">{{ action.assigned_to.get_full_name }}</p>
|
|
<p class="text-xs text-slate-500">{% trans "Assigned" %} {{ action.assigned_at|date:"M d, Y" }}</p>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-slate italic">{% trans "Unassigned" %}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% if action.approved_by %}
|
|
<div class="pt-3 border-t border-slate-100">
|
|
<p class="text-xs font-semibold text-slate-400 uppercase mb-1">{% trans "Approved By" %}</p>
|
|
<p class="font-bold text-navy">{{ action.approved_by.get_full_name }}</p>
|
|
<p class="text-xs text-slate-500">{{ action.approved_at|date:"M d, Y H:i" }}</p>
|
|
</div>
|
|
{% endif %}
|
|
{% if action.closed_by %}
|
|
<div class="pt-3 border-t border-slate-100">
|
|
<p class="text-xs font-semibold text-slate-400 uppercase mb-1">{% trans "Closed By" %}</p>
|
|
<p class="font-bold text-navy">{{ action.closed_by.get_full_name }}</p>
|
|
<p class="text-xs text-slate-500">{{ action.closed_at|date:"M d, Y H:i" }}</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- QI Project -->
|
|
{% if user.is_px_admin or user.is_hospital_admin or user.is_department_manager %}
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="kanban" class="w-4 h-4"></i> {% trans "QI Project" %}
|
|
</h3>
|
|
{% if action.qi_projects.exists %}
|
|
<ul class="space-y-2 mb-3">
|
|
{% for project in action.qi_projects.all %}
|
|
<li><a href="{% url 'projects:project_detail' pk=project.pk %}" class="flex items-center gap-2 text-navy hover:underline text-sm"><i data-lucide="link" class="w-4 h-4"></i>{{ project.name }}</a></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p class="text-sm text-slate-500 mb-3">{% trans "Convert this action to a QI project for long-term tracking." %}</p>
|
|
{% endif %}
|
|
<a href="{% url 'projects:convert_action' action_pk=action.pk %}" class="w-full {% if action.qi_projects.exists %}bg-slate-200 text-slate-400 cursor-not-allowed{% else %}bg-blue-50 border border-blue-200 text-blue-700 hover:bg-blue-100{% endif %} px-4 py-2.5 rounded-xl font-semibold transition flex items-center justify-center gap-2 text-sm">
|
|
<i data-lucide="arrow-right-circle" class="w-4 h-4"></i>
|
|
{% if action.qi_projects.exists %}{% trans "Already Converted" %}{% else %}{% trans "Convert to Project" %}{% endif %}
|
|
</a>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<!-- Add Note -->
|
|
<section class="bg-white rounded-2xl p-6 shadow-sm border border-slate-100">
|
|
<h3 class="font-bold text-navy mb-4 text-sm flex items-center gap-2">
|
|
<i data-lucide="message-square" class="w-4 h-4"></i> {% trans "Add Note" %}
|
|
</h3>
|
|
<form method="post" action="{% url 'actions:action_add_note' action.id %}">
|
|
{% csrf_token %}
|
|
<textarea name="note" class="w-full px-3 py-2 border border-slate-200 rounded-xl text-sm mb-2 focus:ring-2 focus:ring-navy/20 outline-none" rows="3" placeholder="{% trans 'Enter your note...' %}" required></textarea>
|
|
<button type="submit" class="w-full bg-navy text-white px-4 py-2.5 rounded-xl font-semibold hover:bg-blue transition flex items-center justify-center gap-2 text-sm">
|
|
<i data-lucide="plus-circle" class="w-4 h-4"></i> {% trans "Add Note" %}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Escalate Modal -->
|
|
<div id="escalateModal" style="display:none" class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-2xl shadow-2xl max-w-md w-full">
|
|
<form method="post" action="{% url 'actions:action_escalate' action.id %}">
|
|
{% csrf_token %}
|
|
<div class="p-5 border-b border-slate-100">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-red-100 rounded-full flex items-center justify-center"><i data-lucide="arrow-up-circle" class="w-5 h-5 text-red-500"></i></div>
|
|
<h3 class="text-xl font-bold text-navy">{% trans "Escalate Action" %}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="p-5">
|
|
<div class="bg-orange-50 border border-orange-200 rounded-xl p-3 mb-4 text-sm text-orange-700">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4 inline mr-2"></i>
|
|
{% trans "This will escalate the action to the next level." %}
|
|
{% if action.escalation_level > 0 %}({% trans "Current level:" %} {{ action.escalation_level }}){% endif %}
|
|
</div>
|
|
<label class="block text-sm font-semibold text-slate-700 mb-2">{% trans "Reason for Escalation" %} <span class="text-red-500">*</span></label>
|
|
<textarea name="reason" class="w-full border border-slate-200 rounded-xl p-3 text-sm focus:ring-2 focus:ring-navy/20 outline-none" rows="3" placeholder="{% trans 'Explain why this action needs escalation...' %}" required></textarea>
|
|
</div>
|
|
<div class="p-5 border-t border-slate-100 flex gap-3">
|
|
<button type="button" onclick="document.getElementById('escalateModal').style.display='none'" class="flex-1 px-4 py-2.5 border border-slate-200 text-slate-600 rounded-xl font-semibold hover:bg-slate-50 transition">{% trans "Cancel" %}</button>
|
|
<button type="submit" class="flex-1 px-4 py-2.5 bg-red-500 text-white rounded-xl font-semibold hover:bg-red-600 transition flex items-center justify-center gap-2"><i data-lucide="arrow-up-circle" class="w-4 h-4"></i> {% trans "Escalate" %}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Switching -->
|
|
<script>
|
|
function switchTab(tabName) {
|
|
document.querySelectorAll('.tab-panel').forEach(panel => { panel.classList.add('hidden'); });
|
|
document.getElementById('panel-' + tabName).classList.remove('hidden');
|
|
document.querySelectorAll('main nav button').forEach(tab => {
|
|
tab.classList.remove('tab-active');
|
|
tab.classList.add('tab-inactive');
|
|
});
|
|
var activeBtn = document.getElementById('tab-' + tabName);
|
|
if (activeBtn) { activeBtn.classList.remove('tab-inactive'); activeBtn.classList.add('tab-active'); }
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
</script>
|
|
|
|
{% endblock %}
|