360 lines
15 KiB
HTML
360 lines
15 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n static %}
|
|
|
|
{% block title %}{% trans "HIS Logs" %} - PX360{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.stats-card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #7f8c8d;
|
|
font-size: 0.9rem;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.stat-success { color: #27ae60; }
|
|
.stat-failed { color: #e74c3c; }
|
|
.stat-partial { color: #f39c12; }
|
|
|
|
.channel-badge {
|
|
display: inline-block;
|
|
padding: 4px 12px;
|
|
border-radius: 12px;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.channel-email { background: #e3f2fd; color: #1976d2; }
|
|
.channel-sms { background: #fff3e0; color: #f57c00; }
|
|
.channel-his_event { background: #e8f5e9; color: #388e3c; }
|
|
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: 4px 12px;
|
|
border-radius: 12px;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-success, .status-sent { background: #d4edda; color: #155724; }
|
|
.status-failed { background: #f8d7da; color: #721c24; }
|
|
.status-partial { background: #fff3cd; color: #856404; }
|
|
|
|
.filter-section {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.table-hover tbody tr:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.json-preview {
|
|
background: #f5f5f5;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
font-size: 0.85rem;
|
|
max-height: 100px;
|
|
overflow: auto;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<h2><i class="fas fa-robot"></i> {% trans "HIS Logs" %}</h2>
|
|
<p class="text-muted">{% trans "View all HIS requests and responses" %}</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
{% 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." %}');">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger">
|
|
<i class="fas fa-trash"></i> {% trans "Clear All Logs" %}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Dashboard -->
|
|
<div class="row">
|
|
<div class="col-md-2">
|
|
<div class="stats-card text-center">
|
|
<div class="stat-value">{{ stats.total }}</div>
|
|
<div class="stat-label">{% trans "Total Requests" %}</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="stats-card text-center">
|
|
<div class="stat-value stat-success">{{ stats.success }}</div>
|
|
<div class="stat-label">{% trans "Success" %}</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="stats-card text-center">
|
|
<div class="stat-value stat-failed">{{ stats.failed }}</div>
|
|
<div class="stat-label">{% trans "Failed" %}</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="stats-card text-center">
|
|
<div class="stat-value stat-partial">{{ stats.partial }}</div>
|
|
<div class="stat-label">{% trans "Partial" %}</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="stats-card text-center">
|
|
<div class="stat-value">{{ stats.success_rate }}%</div>
|
|
<div class="stat-label">{% trans "Success Rate" %}</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="stats-card text-center">
|
|
<div class="stat-value">{{ stats.avg_processing_time }}ms</div>
|
|
<div class="stat-label">{% trans "Avg. Process Time" %}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Channel Breakdown -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="stats-card">
|
|
<h6>{% trans "By Channel" %}</h6>
|
|
<div class="mt-3">
|
|
<span class="channel-badge channel-email me-2">
|
|
📧 {% trans "Email" %}: {{ stats.channels.email }}
|
|
</span>
|
|
<span class="channel-badge channel-sms me-2">
|
|
📱 {% trans "SMS" %}: {{ stats.channels.sms }}
|
|
</span>
|
|
<span class="channel-badge channel-his_event">
|
|
🏥 {% trans "HIS Events" %}: {{ stats.channels.his_event }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="stats-card">
|
|
<h6>{% trans "By Status" %}</h6>
|
|
<div class="mt-3">
|
|
{% for stat in status_stats %}
|
|
<span class="status-badge status-{{ stat.status }} me-2">
|
|
{{ stat.status|title }}: {{ stat.count }}
|
|
</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="stats-card">
|
|
<h6>{% trans "By Hospital" %}</h6>
|
|
<div class="mt-3">
|
|
{% for stat in hospital_stats|slice:":3" %}
|
|
<span class="badge badge-secondary me-2">
|
|
{{ stat.hospital_code }}: {{ stat.count }}
|
|
</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="filter-section">
|
|
<form method="get" class="row g-3">
|
|
<div class="col-md-2">
|
|
<label class="form-label">{% trans "Channel" %}</label>
|
|
<select name="channel" class="form-select">
|
|
<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 class="col-md-2">
|
|
<label class="form-label">{% trans "Status" %}</label>
|
|
<select name="status" class="form-select">
|
|
<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 class="col-md-2">
|
|
<label class="form-label">{% trans "Visit Type" %}</label>
|
|
<select name="visit_type" class="form-select">
|
|
<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 class="col-md-2">
|
|
<label class="form-label">{% trans "Date From" %}</label>
|
|
<input type="date" name="date_from" class="form-control" value="{{ filters.date_from }}">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">{% trans "Date To" %}</label>
|
|
<input type="date" name="date_to" class="form-control" value="{{ filters.date_to }}">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">{% trans "Search" %}</label>
|
|
<input type="text" name="search" class="form-control" placeholder="ID, MRN, recipient..." value="{{ filters.search }}">
|
|
</div>
|
|
<div class="col-md-1 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-primary w-100">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Logs Table -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>{% trans "ID" %}</th>
|
|
<th>{% trans "Timestamp" %}</th>
|
|
<th>{% trans "Channel" %}</th>
|
|
<th>{% trans "Status" %}</th>
|
|
<th>{% trans "Summary" %}</th>
|
|
<th>{% trans "Details" %}</th>
|
|
<th>{% trans "Process Time" %}</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for log in logs %}
|
|
<tr>
|
|
<td><strong>{{ log.request_id }}</strong></td>
|
|
<td>{{ log.timestamp|date:"Y-m-d H:i:s" }}</td>
|
|
<td>
|
|
<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>
|
|
<span class="status-badge status-{{ log.status }}">
|
|
{{ log.get_status_display_with_icon }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<strong>{{ log.get_summary }}</strong>
|
|
{% if log.patient_id %}
|
|
<br><small class="text-muted">MRN: {{ log.patient_id }}</small>
|
|
{% endif %}
|
|
{% if log.journey_id %}
|
|
<br><small class="text-muted">Journey: {{ log.journey_id }}</small>
|
|
{% endif %}
|
|
{% if log.survey_id %}
|
|
<br><small class="text-muted">Survey: {{ log.survey_id }}</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if log.message_preview %}
|
|
<div class="json-preview">{{ log.message_preview|truncatechars:100 }}</div>
|
|
{% elif log.subject %}
|
|
<div><strong>{{ log.subject|truncatechars:50 }}</strong></div>
|
|
{% elif log.event_type %}
|
|
<div><strong>{{ log.event_type }}</strong></div>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if log.processing_time_ms %}
|
|
{{ log.processing_time_ms }}ms
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'simulator:log_detail' log.request_id %}" class="btn btn-sm btn-info">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="8" class="text-center py-4">
|
|
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
|
|
<p class="text-muted">{% trans "No HIS logs found." %}</p>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<nav>
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
|
|
<i class="fas fa-angle-double-left"></i>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
|
|
<i class="fas fa-angle-left"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ num }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
|
|
<i class="fas fa-angle-right"></i>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% for key, value in filters.items %}&{{ key }}={{ value }}{% endfor %}">
|
|
<i class="fas fa-angle-double-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|