206 lines
13 KiB
HTML
206 lines
13 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{{ department.get_localized_name }} - {% trans "Inquiries" %} - PX360{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="p-6">
|
|
<div class="flex items-center gap-2 text-sm text-slate-500 mb-4">
|
|
<a href="{% url 'organizations:department_list' %}" class="hover:text-navy transition">{% trans "Departments" %}</a>
|
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
|
<a href="{% url 'organizations:department_detail' department.pk %}" class="hover:text-navy transition">{{ department.get_localized_name }}</a>
|
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
|
<span class="text-navy font-semibold">{% trans "Inquiries" %}</span>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-navy">{% trans "Department Inquiries" %}</h1>
|
|
<p class="text-sm text-slate mt-1">{{ department.get_localized_name }} · {{ department.hospital.name }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Incoming / Outgoing tabs -->
|
|
<div class="flex gap-2 mb-4">
|
|
<a href="?direction=incoming" data-dir="incoming"
|
|
class="dir-tab px-4 py-2 rounded-xl text-sm font-semibold border transition flex items-center gap-2
|
|
{% if direction != 'outgoing' %}bg-navy text-white border-navy
|
|
{% else %}bg-white text-slate-600 border-slate-200 hover:border-navy{% endif %}">
|
|
<i data-lucide="inbox" class="w-4 h-4"></i> {% trans "Incoming" %}
|
|
</a>
|
|
<a href="?direction=outgoing" data-dir="outgoing"
|
|
class="dir-tab px-4 py-2 rounded-xl text-sm font-semibold border transition flex items-center gap-2
|
|
{% if direction == 'outgoing' %}bg-navy text-white border-navy
|
|
{% else %}bg-white text-slate-600 border-slate-200 hover:border-navy{% endif %}">
|
|
<i data-lucide="send" class="w-4 h-4"></i> {% trans "Outgoing" %}
|
|
</a>
|
|
</div>
|
|
<script>
|
|
document.querySelectorAll('.dir-tab').forEach(function(tab) {
|
|
var params = new URLSearchParams(window.location.search);
|
|
params.delete('page');
|
|
params.set('direction', tab.dataset.dir);
|
|
tab.href = '?' + params.toString();
|
|
});
|
|
</script>
|
|
|
|
<form method="get" class="bg-white rounded-t-2xl border border-slate-200 border-b-0 px-4 py-3">
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<div class="relative flex-1 min-w-[200px]">
|
|
<i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
|
<input type="text" name="search" value="{{ search_query|default:'' }}" placeholder="{% trans 'Search inquiries...' %}"
|
|
class="pl-9 pr-4 py-2 w-full border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
|
|
</div>
|
|
<select name="status" onchange="this.form.submit()"
|
|
class="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
|
|
<option value="">{% trans "All Statuses" %}</option>
|
|
<option value="open" {% if status_filter == 'open' %}selected{% endif %}>{% trans "Open" %}</option>
|
|
<option value="in_progress" {% if status_filter == 'in_progress' %}selected{% endif %}>{% trans "In Progress" %}</option>
|
|
<option value="resolved" {% if status_filter == 'resolved' %}selected{% endif %}>{% trans "Resolved" %}</option>
|
|
<option value="closed" {% if status_filter == 'closed' %}selected{% endif %}>{% trans "Closed" %}</option>
|
|
<option value="contact_no_response" {% if status_filter == 'contact_no_response' %}selected{% endif %}>{% trans "Contacted - No Response" %}</option>
|
|
</select>
|
|
<select name="priority" onchange="this.form.submit()"
|
|
class="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
|
|
<option value="">{% trans "All Priorities" %}</option>
|
|
<option value="low" {% if priority_filter == 'low' %}selected{% endif %}>{% trans "Low" %}</option>
|
|
<option value="medium" {% if priority_filter == 'medium' %}selected{% endif %}>{% trans "Medium" %}</option>
|
|
<option value="high" {% if priority_filter == 'high' %}selected{% endif %}>{% trans "High" %}</option>
|
|
</select>
|
|
<input type="date" name="date_from" value="{{ date_from }}" placeholder="{% trans 'From' %}"
|
|
class="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
|
|
<input type="date" name="date_to" value="{{ date_to }}" placeholder="{% trans 'To' %}"
|
|
class="px-3 py-2 border border-slate-200 rounded-lg text-sm focus:ring-2 focus:ring-navy/20 outline-none">
|
|
<button type="submit" class="px-3 py-2 bg-navy text-white rounded-lg text-sm font-semibold hover:bg-blue transition">
|
|
<i data-lucide="filter" class="w-3 h-3 inline"></i> {% trans "Filter" %}
|
|
</button>
|
|
{% if search_query or status_filter or priority_filter or date_from or date_to %}
|
|
<a href="?" class="text-xs text-slate-400 hover:text-red-500 transition font-semibold">
|
|
<i data-lucide="x" class="w-3 h-3 inline"></i> {% trans "Clear" %}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
|
|
{% include "partials/list_stats_bar.html" %}
|
|
|
|
<div class="bg-white border-x border-slate-200">
|
|
<table class="w-full">
|
|
<thead class="bg-slate-50 border-b">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Reference" %}</th>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Subject" %}</th>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Contact" %}</th>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Assigned To" %}</th>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Status" %}</th>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Priority" %}</th>
|
|
<th class="px-4 py-3 text-center text-[10px] font-bold text-slate uppercase">{% trans "Response SLA" %}</th>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Created" %}</th>
|
|
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Actions" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-100">
|
|
{% for i in inquiries %}
|
|
<tr class="hover:bg-slate-50 transition border-l-3 border-transparent hover:border-navy">
|
|
<td class="px-4 py-3 text-sm font-mono text-navy">{{ i.reference_number }}</td>
|
|
<td class="px-4 py-3 text-sm text-slate-700 max-w-xs truncate">{{ i.subject }}</td>
|
|
<td class="px-4 py-3 text-sm text-slate">{{ i.contact_name|default:"-" }}</td>
|
|
<td class="px-4 py-3 text-sm text-slate">{{ i.assigned_to.get_full_name|default:"-" }}</td>
|
|
<td class="px-4 py-3">
|
|
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
|
|
{% if i.status == 'open' %}bg-blue-50 text-blue-700
|
|
{% elif i.status == 'in_progress' %}bg-amber-50 text-amber-700
|
|
{% elif i.status == 'resolved' %}bg-green-50 text-green-700
|
|
{% elif i.status == 'contact_no_response' %}bg-amber-100 text-amber-700
|
|
{% else %}bg-slate-50 text-slate-500{% endif %}">
|
|
{{ i.get_status_display }}
|
|
</span>
|
|
{% if i.contact_status == 'contacted_no_response' %}
|
|
<span class="block mt-1 px-2 py-0.5 rounded-full text-[9px] font-bold uppercase bg-amber-100 text-amber-700 w-fit">{% trans "No Response" %}</span>
|
|
{% elif i.contact_status == 'contacted' %}
|
|
<span class="block mt-1 px-2 py-0.5 rounded-full text-[9px] font-bold uppercase bg-green-50 text-green-600 w-fit">{% trans "Contacted" %}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span class="px-2 py-1 rounded-full text-[10px] font-bold uppercase
|
|
{% if i.priority == 'high' %}bg-red-50 text-red-700
|
|
{% elif i.priority == 'medium' %}bg-amber-50 text-amber-700
|
|
{% else %}bg-green-50 text-green-700{% endif %}">
|
|
{{ i.get_priority_display }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-center">
|
|
{% if i.dept_response_sla_due_at and not i.department_responded_at %}
|
|
<span class="sla-timer text-[10px] font-bold" data-due-at="{{ i.dept_response_sla_due_at|date:'c' }}" data-overdue="{{ i.dept_response_is_overdue|yesno:'true,false' }}"></span>
|
|
{% elif i.dept_response_is_overdue %}
|
|
<span class="text-[10px] font-bold text-red-600">{% trans "Overdue" %}</span>
|
|
{% elif i.department_responded_at %}
|
|
<span class="text-[10px] text-green-600 font-semibold">✓</span>
|
|
{% else %}
|
|
<span class="text-[10px] text-slate-400">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="px-4 py-3 text-xs text-slate">{{ i.created_at|date:"Y-m-d" }}</td>
|
|
<td class="px-4 py-3">
|
|
<a href="{% url 'organizations:department_inquiry_detail' department.pk i.pk %}" class="text-xs text-navy font-semibold hover:underline">{% trans "View" %}</a>
|
|
{% if can_respond and i.handling_department_id == department.pk and i.status in 'open,in_progress' %}
|
|
<a href="{% url 'organizations:department_inquiry_detail' department.pk i.pk %}" class="text-xs text-green-600 font-semibold hover:underline ml-2">{% trans "Handle" %}</a>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="9" class="px-4 py-12 text-center text-slate">{% trans "No inquiries found" %}</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% include "partials/pagination.html" %}
|
|
|
|
{% include "components/department_response_modal.html" %}
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
var timers = document.querySelectorAll('.sla-timer');
|
|
if (!timers.length) return;
|
|
function updateTimers() {
|
|
var now = Date.now();
|
|
timers.forEach(function(el) {
|
|
var dueAt = new Date(el.dataset.dueAt).getTime();
|
|
var diff = dueAt - now;
|
|
if (diff <= 0) {
|
|
var overMs = Math.abs(diff);
|
|
var overD = Math.floor(overMs / 86400000);
|
|
var overH = Math.floor((overMs % 86400000) / 3600000);
|
|
var overM = Math.floor((overMs % 3600000) / 60000);
|
|
var txt = '{% trans "Overdue" %} ';
|
|
if (overD > 0) txt += overD + 'd ';
|
|
txt += overH + 'h ' + overM + 'm';
|
|
el.textContent = txt;
|
|
el.className = 'sla-timer text-[10px] font-bold text-red-600';
|
|
return;
|
|
}
|
|
var d = Math.floor(diff / 86400000);
|
|
var h = Math.floor((diff % 86400000) / 3600000);
|
|
var m = Math.floor((diff % 3600000) / 60000);
|
|
var parts = [];
|
|
if (d > 0) parts.push(d + 'd');
|
|
parts.push(h + 'h');
|
|
parts.push(m + 'm');
|
|
el.textContent = parts.join(' ');
|
|
if (diff < 21600000) {
|
|
el.className = 'sla-timer text-[10px] font-bold text-red-600';
|
|
} else if (diff < 86400000) {
|
|
el.className = 'sla-timer text-[10px] font-bold text-orange-600';
|
|
} else {
|
|
el.className = 'sla-timer text-[10px] font-bold text-green-600';
|
|
}
|
|
});
|
|
}
|
|
updateTimers();
|
|
setInterval(updateTimers, 60000);
|
|
})();
|
|
</script>
|
|
{% endblock %}
|