hospital-management/templates/communications/alert_instance_detail.html
2025-08-12 13:33:25 +03:00

790 lines
39 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}{{ object.message|truncatechars:50 }} - Alert Instance Details{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Breadcrumb -->
<div class="row">
<div class="col-12">
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
<h4 class="mb-sm-0">Alert Instance Details</h4>
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'communications:dashboard' %}">Communications</a></li>
<li class="breadcrumb-item"><a href="{% url 'communications:alert_instance_list' %}">Alert Instances</a></li>
<li class="breadcrumb-item active">Details</li>
</ol>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Main Content -->
<div class="col-lg-8">
<!-- Alert Overview -->
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">
<i class="fas fa-{% if object.alert_rule.severity == 'CRITICAL' %}exclamation-circle{% elif object.alert_rule.severity == 'HIGH' %}exclamation-triangle{% else %}bell{% endif %} me-2"></i>
Alert Instance
</h5>
<div class="d-flex gap-2">
<span class="badge bg-{% if object.alert_rule.category == 'SYSTEM' %}primary{% elif object.alert_rule.category == 'PATIENT' %}success{% elif object.alert_rule.category == 'EQUIPMENT' %}warning{% elif object.alert_rule.category == 'MEDICATION' %}info{% else %}secondary{% endif %} fs-6">
{{ object.alert_rule.get_category_display }}
</span>
<span class="badge bg-{% if object.alert_rule.severity == 'CRITICAL' %}danger{% elif object.alert_rule.severity == 'HIGH' %}warning{% elif object.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %} fs-6">
{{ object.alert_rule.get_severity_display }}
</span>
<span class="badge bg-{% if object.status == 'RESOLVED' %}success{% elif object.status == 'ACKNOWLEDGED' %}info{% elif object.status == 'ESCALATED' %}warning{% elif object.status == 'SNOOZED' %}secondary{% else %}danger{% endif %} fs-6">
{{ object.get_status_display }}
</span>
</div>
</div>
</div>
<div class="card-body">
<!-- Alert Message -->
<div class="alert alert-{% if object.alert_rule.severity == 'CRITICAL' %}danger{% elif object.alert_rule.severity == 'HIGH' %}warning{% elif object.alert_rule.severity == 'MEDIUM' %}info{% else %}light{% endif %}" role="alert">
<div class="d-flex">
<div class="flex-shrink-0">
<i class="fas fa-{% if object.alert_rule.severity == 'CRITICAL' %}exclamation-circle{% elif object.alert_rule.severity == 'HIGH' %}exclamation-triangle{% else %}info-circle{% endif %} fa-2x"></i>
</div>
<div class="flex-grow-1 ms-3">
<h5 class="alert-heading">{{ object.message }}</h5>
{% if object.details %}
<p class="mb-0">{{ object.details }}</p>
{% endif %}
</div>
</div>
</div>
<!-- Alert Information -->
<div class="row mb-4">
<div class="col-md-6">
<div class="table-responsive">
<table class="table table-borderless">
<tr>
<td class="fw-bold text-muted">Alert Rule:</td>
<td>
<a href="{% url 'communications:alert_rule_detail' object.alert_rule.pk %}" class="text-primary">
{{ object.alert_rule.rule_name }}
</a>
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Category:</td>
<td>{{ object.alert_rule.get_category_display }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Severity:</td>
<td>{{ object.alert_rule.get_severity_display }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Status:</td>
<td>{{ object.get_status_display }}</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
<div class="table-responsive">
<table class="table table-borderless">
<tr>
<td class="fw-bold text-muted">Triggered:</td>
<td>{{ object.triggered_at|date:"M d, Y g:i A" }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Time Ago:</td>
<td>{{ object.triggered_at|timesince }} ago</td>
</tr>
<tr>
<td class="fw-bold text-muted">Response Time:</td>
<td>
{% if object.acknowledged_at %}
{{ object.response_time_minutes|default:0 }} minutes
{% else %}
<span class="text-muted">Not acknowledged</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Priority:</td>
<td>
<span class="badge bg-{% if object.priority == 'HIGH' %}danger{% elif object.priority == 'MEDIUM' %}warning{% else %}secondary{% endif %}">
{{ object.priority|default:"Normal" }}
</span>
</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Trigger Data -->
{% if object.trigger_data %}
<div class="mb-4">
<h6 class="text-muted mb-2">Trigger Data:</h6>
<div class="bg-light p-3 rounded">
<pre class="mb-0">{{ object.trigger_data|pprint }}</pre>
</div>
</div>
{% endif %}
<!-- Context Data -->
{% if object.context_data %}
<div class="mb-4">
<h6 class="text-muted mb-2">Context Information:</h6>
<div class="bg-light p-3 rounded">
<pre class="mb-0">{{ object.context_data|pprint }}</pre>
</div>
</div>
{% endif %}
<!-- Acknowledgment Information -->
{% if object.acknowledged_at %}
<div class="mb-4">
<h6 class="text-muted mb-2">Acknowledgment Details:</h6>
<div class="card bg-light">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold text-muted">Acknowledged By:</td>
<td>
{% if object.acknowledged_by %}
<div class="d-flex align-items-center">
<div class="avatar-xs me-2">
<div class="avatar-title bg-soft-success text-success rounded-circle">
{{ object.acknowledged_by.first_name.0 }}{{ object.acknowledged_by.last_name.0 }}
</div>
</div>
<div>
<h6 class="mb-0">{{ object.acknowledged_by.get_full_name }}</h6>
<small class="text-muted">{{ object.acknowledged_by.email }}</small>
</div>
</div>
{% else %}
<span class="text-muted">System</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Acknowledged At:</td>
<td>{{ object.acknowledged_at|date:"M d, Y g:i A" }}</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
{% if object.acknowledgment_note %}
<div>
<h6 class="text-muted">Acknowledgment Note:</h6>
<div class="bg-white p-2 rounded border">
{{ object.acknowledgment_note|linebreaks }}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Resolution Information -->
{% if object.resolved_at %}
<div class="mb-4">
<h6 class="text-muted mb-2">Resolution Details:</h6>
<div class="card bg-light">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold text-muted">Resolved By:</td>
<td>
{% if object.resolved_by %}
<div class="d-flex align-items-center">
<div class="avatar-xs me-2">
<div class="avatar-title bg-soft-success text-success rounded-circle">
{{ object.resolved_by.first_name.0 }}{{ object.resolved_by.last_name.0 }}
</div>
</div>
<div>
<h6 class="mb-0">{{ object.resolved_by.get_full_name }}</h6>
<small class="text-muted">{{ object.resolved_by.email }}</small>
</div>
</div>
{% else %}
<span class="text-muted">System</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Resolved At:</td>
<td>{{ object.resolved_at|date:"M d, Y g:i A" }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Resolution Time:</td>
<td>{{ object.resolution_time_minutes|default:0 }} minutes</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
{% if object.resolution_note %}
<div>
<h6 class="text-muted">Resolution Note:</h6>
<div class="bg-white p-2 rounded border">
{{ object.resolution_note|linebreaks }}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Snooze Information -->
{% if object.status == 'SNOOZED' and object.snoozed_until %}
<div class="mb-4">
<h6 class="text-muted mb-2">Snooze Information:</h6>
<div class="alert alert-info" role="alert">
<div class="d-flex">
<div class="flex-shrink-0">
<i class="fas fa-clock fa-lg"></i>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="alert-heading">Alert Snoozed</h6>
<p class="mb-1">This alert is snoozed until: <strong>{{ object.snoozed_until|date:"M d, Y g:i A" }}</strong></p>
<p class="mb-0">
{% if object.snoozed_by %}
Snoozed by: {{ object.snoozed_by.get_full_name }}
{% endif %}
</p>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Alert Timeline -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-history me-2"></i>
Alert Timeline
</h5>
</div>
<div class="card-body">
<div class="timeline">
<!-- Alert Triggered -->
<div class="timeline-item">
<div class="timeline-marker bg-{% if object.alert_rule.severity == 'CRITICAL' %}danger{% elif object.alert_rule.severity == 'HIGH' %}warning{% elif object.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %}">
<i class="fas fa-bell"></i>
</div>
<div class="timeline-content">
<div class="card">
<div class="card-body">
<h6 class="mb-1">Alert Triggered</h6>
<p class="text-muted mb-1">{{ object.message }}</p>
<small class="text-muted">{{ object.triggered_at|date:"M d, Y g:i A" }}</small>
</div>
</div>
</div>
</div>
<!-- Alert Acknowledged -->
{% if object.acknowledged_at %}
<div class="timeline-item">
<div class="timeline-marker bg-info">
<i class="fas fa-check"></i>
</div>
<div class="timeline-content">
<div class="card">
<div class="card-body">
<h6 class="mb-1">Alert Acknowledged</h6>
<p class="text-muted mb-1">
{% if object.acknowledged_by %}
By {{ object.acknowledged_by.get_full_name }}
{% else %}
By System
{% endif %}
</p>
{% if object.acknowledgment_note %}
<p class="mb-1">{{ object.acknowledgment_note }}</p>
{% endif %}
<small class="text-muted">{{ object.acknowledged_at|date:"M d, Y g:i A" }}</small>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Alert Escalated -->
{% if object.escalated_at %}
<div class="timeline-item">
<div class="timeline-marker bg-warning">
<i class="fas fa-arrow-up"></i>
</div>
<div class="timeline-content">
<div class="card">
<div class="card-body">
<h6 class="mb-1">Alert Escalated</h6>
<p class="text-muted mb-1">
{% if object.escalated_by %}
By {{ object.escalated_by.get_full_name }}
{% else %}
Automatic escalation
{% endif %}
</p>
<small class="text-muted">{{ object.escalated_at|date:"M d, Y g:i A" }}</small>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Alert Resolved -->
{% if object.resolved_at %}
<div class="timeline-item">
<div class="timeline-marker bg-success">
<i class="fas fa-check-circle"></i>
</div>
<div class="timeline-content">
<div class="card">
<div class="card-body">
<h6 class="mb-1">Alert Resolved</h6>
<p class="text-muted mb-1">
{% if object.resolved_by %}
By {{ object.resolved_by.get_full_name }}
{% else %}
By System
{% endif %}
</p>
{% if object.resolution_note %}
<p class="mb-1">{{ object.resolution_note }}</p>
{% endif %}
<small class="text-muted">{{ object.resolved_at|date:"M d, Y g:i A" }}</small>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Related Alerts -->
{% if related_alerts %}
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-link me-2"></i>
Related Alerts
</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-nowrap table-hover mb-0">
<thead class="table-light">
<tr>
<th>Alert</th>
<th>Rule</th>
<th>Status</th>
<th>Triggered</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for alert in related_alerts %}
<tr>
<td>{{ alert.message|truncatechars:40 }}</td>
<td>{{ alert.alert_rule.rule_name|truncatechars:25 }}</td>
<td>
<span class="badge bg-{% if alert.status == 'RESOLVED' %}success{% elif alert.status == 'ACKNOWLEDGED' %}info{% elif alert.status == 'ESCALATED' %}warning{% elif alert.status == 'SNOOZED' %}secondary{% else %}danger{% endif %}">
{{ alert.get_status_display }}
</span>
</td>
<td>{{ alert.triggered_at|timesince }} ago</td>
<td>
<a href="{% url 'communications:alert_instance_detail' alert.pk %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-bolt me-2"></i>
Quick Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if object.status == 'ACTIVE' %}
<button class="btn btn-success" onclick="acknowledgeAlert()">
<i class="fas fa-check me-1"></i>
Acknowledge Alert
</button>
<button class="btn btn-primary" onclick="resolveAlert()">
<i class="fas fa-check-circle me-1"></i>
Resolve Alert
</button>
<button class="btn btn-outline-warning" onclick="snoozeAlert()">
<i class="fas fa-clock me-1"></i>
Snooze Alert
</button>
<button class="btn btn-outline-danger" onclick="escalateAlert()">
<i class="fas fa-arrow-up me-1"></i>
Escalate Alert
</button>
{% elif object.status == 'ACKNOWLEDGED' %}
<button class="btn btn-primary" onclick="resolveAlert()">
<i class="fas fa-check-circle me-1"></i>
Resolve Alert
</button>
<button class="btn btn-outline-danger" onclick="escalateAlert()">
<i class="fas fa-arrow-up me-1"></i>
Escalate Alert
</button>
{% elif object.status == 'SNOOZED' %}
<button class="btn btn-outline-success" onclick="unsnoozeAlert()">
<i class="fas fa-play me-1"></i>
Unsnooze Alert
</button>
{% endif %}
<hr>
<button class="btn btn-outline-secondary" onclick="addComment()">
<i class="fas fa-comment me-1"></i>
Add Comment
</button>
<button class="btn btn-outline-info" onclick="exportAlert()">
<i class="fas fa-download me-1"></i>
Export Details
</button>
<button class="btn btn-outline-secondary" onclick="shareAlert()">
<i class="fas fa-share me-1"></i>
Share Alert
</button>
</div>
</div>
</div>
<!-- Alert Statistics -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-bar me-2"></i>
Alert Statistics
</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold text-muted">Response Time:</td>
<td>
{% if object.acknowledged_at %}
{{ object.response_time_minutes|default:0 }} min
{% else %}
<span class="text-muted">Pending</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Resolution Time:</td>
<td>
{% if object.resolved_at %}
{{ object.resolution_time_minutes|default:0 }} min
{% else %}
<span class="text-muted">Pending</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Age:</td>
<td>{{ object.triggered_at|timesince }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Notifications Sent:</td>
<td>{{ object.notifications_sent|default:0 }}</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Alert Rule Info -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-cog me-2"></i>
Alert Rule
</h5>
</div>
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="avatar-sm me-3">
<div class="avatar-title bg-soft-primary text-primary rounded">
<i class="fas fa-exclamation-triangle"></i>
</div>
</div>
<div>
<h6 class="mb-0">
<a href="{% url 'communications:alert_rule_detail' object.alert_rule.pk %}">
{{ object.alert_rule.rule_name }}
</a>
</h6>
<small class="text-muted">{{ object.alert_rule.get_category_display }}</small>
</div>
</div>
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold text-muted">Condition:</td>
<td><code>{{ object.alert_rule.condition_expression|truncatechars:30 }}</code></td>
</tr>
<tr>
<td class="fw-bold text-muted">Cooldown:</td>
<td>{{ object.alert_rule.cooldown_minutes|default:0 }} min</td>
</tr>
<tr>
<td class="fw-bold text-muted">Auto Resolve:</td>
<td>
{% if object.alert_rule.auto_resolve %}
<span class="text-success"><i class="fas fa-check"></i></span>
{% else %}
<span class="text-muted"><i class="fas fa-times"></i></span>
{% endif %}
</td>
</tr>
</table>
</div>
<div class="mt-3">
<a href="{% url 'communications:alert_rule_detail' object.alert_rule.pk %}" class="btn btn-sm btn-outline-primary">
View Rule Details
</a>
</div>
</div>
</div>
<!-- Recent Similar Alerts -->
{% if similar_alerts %}
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-history me-2"></i>
Recent Similar Alerts
</h5>
</div>
<div class="card-body">
{% for alert in similar_alerts %}
<div class="d-flex align-items-center mb-2">
<div class="avatar-xs me-2">
<div class="avatar-title bg-soft-{% if alert.status == 'RESOLVED' %}success{% elif alert.status == 'ACKNOWLEDGED' %}info{% else %}danger{% endif %} text-{% if alert.status == 'RESOLVED' %}success{% elif alert.status == 'ACKNOWLEDGED' %}info{% else %}danger{% endif %} rounded">
<i class="fas fa-bell"></i>
</div>
</div>
<div class="flex-grow-1">
<h6 class="mb-0">
<a href="{% url 'communications:alert_instance_detail' alert.pk %}">
{{ alert.message|truncatechars:25 }}
</a>
</h6>
<small class="text-muted">{{ alert.triggered_at|timesince }} ago</small>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block css %}
<style>
.timeline {
position: relative;
padding-left: 30px;
}
.timeline::before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: 0;
width: 2px;
background: #dee2e6;
}
.timeline-item {
position: relative;
margin-bottom: 20px;
}
.timeline-marker {
position: absolute;
left: -22px;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
.timeline-content {
margin-left: 20px;
}
</style>
{% endblock %}
{% block js %}
<script>
function acknowledgeAlert() {
const note = prompt('Add an acknowledgment note (optional):');
updateAlertStatus('ACKNOWLEDGED', note);
}
function resolveAlert() {
const note = prompt('Add a resolution note (optional):');
updateAlertStatus('RESOLVED', note);
}
function snoozeAlert() {
const minutes = prompt('Snooze this alert for how many minutes?', '60');
if (minutes && !isNaN(minutes)) {
fetch(`{% url 'communications:alert_instance_detail' object.pk %}snooze/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
body: JSON.stringify({ minutes: parseInt(minutes) })
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error snoozing alert: ' + data.error);
}
});
}
}
function escalateAlert() {
if (confirm('Escalate this alert?')) {
updateAlertStatus('ESCALATED');
}
}
function unsnoozeAlert() {
if (confirm('Unsnooze this alert?')) {
updateAlertStatus('ACTIVE');
}
}
function updateAlertStatus(status, note = null) {
const data = { status: status };
if (note) {
data.note = note;
}
fetch(`{% url 'communications:alert_instance_detail' object.pk %}update-status/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error updating alert: ' + data.error);
}
});
}
function addComment() {
const comment = prompt('Add a comment to this alert:');
if (comment && comment.trim()) {
fetch(`{% url 'communications:alert_instance_detail' object.pk %}add-comment/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
body: JSON.stringify({ comment: comment.trim() })
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Comment added successfully');
location.reload();
} else {
alert('Error adding comment: ' + data.error);
}
});
}
}
function exportAlert() {
window.open(`{% url 'communications:alert_instance_detail' object.pk %}export/`, '_blank');
}
function shareAlert() {
const url = window.location.href;
if (navigator.share) {
navigator.share({
title: 'Alert: {{ object.message|escapejs }}',
text: 'Alert from {{ object.alert_rule.rule_name|escapejs }}',
url: url
});
} else {
// Fallback: copy to clipboard
navigator.clipboard.writeText(url).then(() => {
alert('Alert URL copied to clipboard');
});
}
}
</script>
{% endblock %}