HH/templates/simulator/log_list.html
2026-02-22 08:35:53 +03:00

338 lines
19 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n static %}
{% block title %}{% trans "HIS Logs" %} - PX360{% endblock %}
{% block extra_css %}
<style>
.stats-card {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.stats-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
}
.channel-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 500;
}
.channel-email { background: #eff6ff; color: #3b82f6; }
.channel-sms { background: #fff7ed; color: #f97316; }
.channel-his_event { background: #f0fdf4; color: #22c55e; }
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 500;
}
.status-success, .status-sent { background: #dcfce7; color: #16a34a; }
.status-failed { background: #fee2e2; color: #dc2626; }
.status-partial { background: #fef3c7; color: #d97706; }
.json-preview {
background: #f5f5f5;
padding: 8px;
border-radius: 8px;
font-family: monospace;
font-size: 0.85rem;
max-height: 100px;
overflow: auto;
}
.log-row:hover {
background-color: #fff1f2;
}
</style>
{% endblock %}
{% block content %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Page Header -->
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
<div>
<h2 class="text-3xl font-bold text-gray-800 mb-2 flex items-center gap-2">
<i data-lucide="bot" class="w-8 h-8 text-navy"></i>
{% trans "HIS Logs" %}
</h2>
<p class="text-gray-500">{% trans "View all HIS requests and responses" %}</p>
</div>
{% if user.is_superuser or user.is_px_admin %}
<form method="post" action="{% url 'simulator:clear_logs' %}" onsubmit="return confirm('{% trans "Are you sure you want to clear all HIS logs? This cannot be undone." %}');" class="inline-flex">
{% csrf_token %}
<button type="submit" class="bg-red-500 text-white px-6 py-3 rounded-xl font-bold hover:bg-red-600 transition flex items-center gap-2 shadow-lg shadow-red-200">
<i data-lucide="trash-2" class="w-5 h-5"></i> {% trans "Clear All Logs" %}
</button>
</form>
{% endif %}
</div>
<!-- Statistics Dashboard -->
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
<div class="bg-white p-6 rounded-[2rem] shadow-sm border border-gray-50 stats-card">
<div class="text-4xl font-bold text-gray-800 mb-1">{{ stats.total }}</div>
<div class="text-gray-400 text-sm font-medium">{% trans "Total Requests" %}</div>
</div>
<div class="bg-white p-6 rounded-[2rem] shadow-sm border border-gray-50 stats-card">
<div class="text-4xl font-bold text-emerald-500 mb-1">{{ stats.success }}</div>
<div class="text-gray-400 text-sm font-medium">{% trans "Success" %}</div>
</div>
<div class="bg-white p-6 rounded-[2rem] shadow-sm border border-gray-50 stats-card">
<div class="text-4xl font-bold text-red-500 mb-1">{{ stats.failed }}</div>
<div class="text-gray-400 text-sm font-medium">{% trans "Failed" %}</div>
</div>
<div class="bg-white p-6 rounded-[2rem] shadow-sm border border-gray-50 stats-card">
<div class="text-4xl font-bold text-amber-500 mb-1">{{ stats.partial }}</div>
<div class="text-gray-400 text-sm font-medium">{% trans "Partial" %}</div>
</div>
<div class="bg-white p-6 rounded-[2rem] shadow-sm border border-gray-50 stats-card">
<div class="text-4xl font-bold text-navy mb-1">{{ stats.success_rate }}%</div>
<div class="text-gray-400 text-sm font-medium">{% trans "Success Rate" %}</div>
</div>
<div class="bg-white p-6 rounded-[2rem] shadow-sm border border-gray-50 stats-card">
<div class="text-4xl font-bold text-cyan-500 mb-1">{{ stats.avg_processing_time }}ms</div>
<div class="text-gray-400 text-sm font-medium">{% trans "Avg. Process Time" %}</div>
</div>
</div>
<!-- Channel Breakdown -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-50">
<h3 class="font-bold text-gray-800 mb-4 flex items-center gap-2">
<i data-lucide="hash" class="w-5 h-5 text-navy"></i>
{% trans "By Channel" %}
</h3>
<div class="flex flex-wrap gap-2">
<span class="channel-badge channel-email">
📧 {% trans "Email" %}: {{ stats.channels.email }}
</span>
<span class="channel-badge channel-sms">
📱 {% trans "SMS" %}: {{ stats.channels.sms }}
</span>
<span class="channel-badge channel-his_event">
🏥 {% trans "HIS Events" %}: {{ stats.channels.his_event }}
</span>
</div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-50">
<h3 class="font-bold text-gray-800 mb-4 flex items-center gap-2">
<i data-lucide="check-circle-2" class="w-5 h-5 text-navy"></i>
{% trans "By Status" %}
</h3>
<div class="flex flex-wrap gap-2">
{% for stat in status_stats %}
<span class="status-badge status-{{ stat.status }}">
{{ stat.status|title }}: {{ stat.count }}
</span>
{% endfor %}
</div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-50">
<h3 class="font-bold text-gray-800 mb-4 flex items-center gap-2">
<i data-lucide="building" class="w-5 h-5 text-navy"></i>
{% trans "By Hospital" %}
</h3>
<div class="flex flex-wrap gap-2">
{% for stat in hospital_stats|slice:":3" %}
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold bg-gray-100 text-gray-700">
{{ stat.hospital_code }}: {{ stat.count }}
</span>
{% endfor %}
</div>
</div>
</div>
<!-- Filters -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50 mb-8">
<div class="px-6 py-4 border-b border-gray-100">
<h3 class="font-bold text-gray-800 flex items-center gap-2">
<i data-lucide="filter" class="w-5 h-5 text-navy"></i>
{% trans "Filters" %}
</h3>
</div>
<div class="p-6">
<form method="get" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">{% trans "Channel" %}</label>
<select name="channel" class="w-full px-4 py-2.5 border-2 border-gray-200 rounded-xl text-gray-800 focus:ring-2 focus:ring-navy focus:border-transparent transition">
<option value="">{% trans "All" %}</option>
<option value="email" {% if filters.channel == 'email' %}selected{% endif %}>{% trans "Email" %}</option>
<option value="sms" {% if filters.channel == 'sms' %}selected{% endif %}>{% trans "SMS" %}</option>
<option value="his_event" {% if filters.channel == 'his_event' %}selected{% endif %}>{% trans "HIS Event" %}</option>
</select>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">{% trans "Status" %}</label>
<select name="status" class="w-full px-4 py-2.5 border-2 border-gray-200 rounded-xl text-gray-800 focus:ring-2 focus:ring-navy focus:border-transparent transition">
<option value="">{% trans "All" %}</option>
<option value="success" {% if filters.status == 'success' %}selected{% endif %}>{% trans "Success" %}</option>
<option value="sent" {% if filters.status == 'sent' %}selected{% endif %}>{% trans "Sent" %}</option>
<option value="failed" {% if filters.status == 'failed' %}selected{% endif %}>{% trans "Failed" %}</option>
<option value="partial" {% if filters.status == 'partial' %}selected{% endif %}>{% trans "Partial" %}</option>
</select>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">{% trans "Visit Type" %}</label>
<select name="visit_type" class="w-full px-4 py-2.5 border-2 border-gray-200 rounded-xl text-gray-800 focus:ring-2 focus:ring-navy focus:border-transparent transition">
<option value="">{% trans "All" %}</option>
<option value="opd" {% if filters.visit_type == 'opd' %}selected{% endif %}>OPD</option>
<option value="inpatient" {% if filters.visit_type == 'inpatient' %}selected{% endif %}>Inpatient</option>
<option value="ems" {% if filters.visit_type == 'ems' %}selected{% endif %}>EMS</option>
</select>
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">{% trans "Search" %}</label>
<input type="text" name="search" class="w-full px-4 py-2.5 border-2 border-gray-200 rounded-xl text-gray-800 focus:ring-2 focus:ring-navy focus:border-transparent transition" placeholder="ID, MRN, recipient..." value="{{ filters.search }}">
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">{% trans "Date From" %}</label>
<input type="date" name="date_from" class="w-full px-4 py-2.5 border-2 border-gray-200 rounded-xl text-gray-800 focus:ring-2 focus:ring-navy focus:border-transparent transition" value="{{ filters.date_from }}">
</div>
<div>
<label class="block text-sm font-bold text-gray-700 mb-2">{% trans "Date To" %}</label>
<input type="date" name="date_to" class="w-full px-4 py-2.5 border-2 border-gray-200 rounded-xl text-gray-800 focus:ring-2 focus:ring-navy focus:border-transparent transition" value="{{ filters.date_to }}">
</div>
<div class="lg:col-span-2 flex items-end">
<button type="submit" class="bg-light0 text-white px-6 py-2.5 rounded-xl font-bold hover:bg-navy transition flex items-center gap-2">
<i data-lucide="search" class="w-4 h-4"></i> {% trans "Search" %}
</button>
</div>
</form>
</div>
</div>
<!-- Logs Table -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-50 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "ID" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Timestamp" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Channel" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Status" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Summary" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Details" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider">{% trans "Process Time" %}</th>
<th class="px-6 py-4 text-left text-xs font-bold text-gray-500 uppercase tracking-wider"></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for log in logs %}
<tr class="log-row transition">
<td class="px-6 py-4"><strong class="text-gray-800">{{ log.request_id }}</strong></td>
<td class="px-6 py-4 text-gray-600 text-sm">{{ log.timestamp|date:"Y-m-d H:i:s" }}</td>
<td class="px-6 py-4">
<span class="channel-badge channel-{{ log.channel }}">
{% if log.channel == 'email' %}📧 {% trans "Email" %}
{% elif log.channel == 'sms' %}📱 {% trans "SMS" %}
{% else %}🏥 {% trans "HIS Event" %}{% endif %}
</span>
</td>
<td class="px-6 py-4">
<span class="status-badge status-{{ log.status }}">
{{ log.get_status_display_with_icon }}
</span>
</td>
<td class="px-6 py-4">
<strong class="text-gray-800">{{ log.get_summary }}</strong>
{% if log.patient_id %}
<br><small class="text-gray-500">MRN: {{ log.patient_id }}</small>
{% endif %}
{% if log.journey_id %}
<br><small class="text-gray-500">Journey: {{ log.journey_id }}</small>
{% endif %}
{% if log.survey_id %}
<br><small class="text-gray-500">Survey: {{ log.survey_id }}</small>
{% endif %}
</td>
<td class="px-6 py-4">
{% if log.message_preview %}
<div class="json-preview">{{ log.message_preview|truncatechars:100 }}</div>
{% elif log.subject %}
<div><strong class="text-gray-800">{{ log.subject|truncatechars:50 }}</strong></div>
{% elif log.event_type %}
<div><strong class="text-gray-800">{{ log.event_type }}</strong></div>
{% endif %}
</td>
<td class="px-6 py-4 text-gray-600 text-sm">
{% if log.processing_time_ms %}
{{ log.processing_time_ms }}ms
{% else %}
-
{% endif %}
</td>
<td class="px-6 py-4">
<a href="{% url 'simulator:log_detail' log.request_id %}" class="inline-flex items-center gap-1 px-3 py-2 text-navy bg-light rounded-lg hover:bg-light transition font-medium text-sm">
<i data-lucide="eye" class="w-4 h-4"></i>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-12">
<i data-lucide="inbox" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i>
<p class="text-gray-500">{% trans "No HIS logs found." %}</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<div class="px-6 py-4 border-t border-gray-100 flex justify-center">
<nav>
<ul class="flex gap-1">
{% if page_obj.has_previous %}
<li>
<a class="px-3 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition" href="?page=1{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
</a>
</li>
<li>
<a class="px-3 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition" href="?page={{ page_obj.previous_page_number }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
<i data-lucide="chevron-left" class="w-4 h-4"></i>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li><span class="px-4 py-2 bg-light0 text-white rounded-lg font-bold">{{ num }}</span></li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li>
<a class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition" href="?page={{ num }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li>
<a class="px-3 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition" href="?page={{ page_obj.next_page_number }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
<i data-lucide="chevron-right" class="w-4 h-4"></i>
</a>
</li>
<li>
<a class="px-3 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition" href="?page={{ page_obj.paginator.num_pages }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
});
</script>
{% endblock %}