519 lines
26 KiB
HTML
519 lines
26 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Metric Values - Hospital Management{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="content">
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h4>Metric Values</h4>
|
|
<h6>View and manage calculated metric values and historical data</h6>
|
|
</div>
|
|
<div class="page-btn">
|
|
<a href="{% url 'analytics:calculate_metric' %}" class="btn btn-primary">
|
|
<i class="fas fa-calculator me-1"></i>Calculate Metrics
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row">
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-chart-line text-primary"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5>{{ total_values|default:"0" }}</h5>
|
|
<h6>Total Values</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-clock text-success"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5>{{ recent_calculations|default:"0" }}</h5>
|
|
<h6>Recent Calculations</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-exclamation-triangle text-warning"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5>{{ failed_calculations|default:"0" }}</h5>
|
|
<h6>Failed Calculations</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-calculator text-info"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5>{{ active_metrics|default:"0" }}</h5>
|
|
<h6>Active Metrics</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-filter me-2"></i>Filters
|
|
</h5>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="clearFilters()">
|
|
<i class="fas fa-times me-1"></i>Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="get" id="filterForm">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="metric" class="form-label">Metric</label>
|
|
<select class="form-select" id="metric" name="metric">
|
|
<option value="">All Metrics</option>
|
|
{% for metric in metrics %}
|
|
<option value="{{ metric.id }}" {% if request.GET.metric == metric.id|stringformat:"s" %}selected{% endif %}>
|
|
{{ metric.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<label for="status" class="form-label">Status</label>
|
|
<select class="form-select" id="status" name="status">
|
|
<option value="">All Status</option>
|
|
<option value="success" {% if request.GET.status == 'success' %}selected{% endif %}>Success</option>
|
|
<option value="error" {% if request.GET.status == 'error' %}selected{% endif %}>Error</option>
|
|
<option value="pending" {% if request.GET.status == 'pending' %}selected{% endif %}>Pending</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<label for="date_from" class="form-label">From Date</label>
|
|
<input type="date" class="form-control" id="date_from" name="date_from"
|
|
value="{{ request.GET.date_from }}">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<label for="date_to" class="form-label">To Date</label>
|
|
<input type="date" class="form-control" id="date_to" name="date_to"
|
|
value="{{ request.GET.date_to }}">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<label for="category" class="form-label">Category</label>
|
|
<select class="form-select" id="category" name="category">
|
|
<option value="">All Categories</option>
|
|
<option value="clinical" {% if request.GET.category == 'clinical' %}selected{% endif %}>Clinical</option>
|
|
<option value="financial" {% if request.GET.category == 'financial' %}selected{% endif %}>Financial</option>
|
|
<option value="operational" {% if request.GET.category == 'operational' %}selected{% endif %}>Operational</option>
|
|
<option value="quality" {% if request.GET.category == 'quality' %}selected{% endif %}>Quality</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-1">
|
|
<div class="form-group">
|
|
<label class="form-label"> </label>
|
|
<button type="submit" class="btn btn-primary d-block">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metric Values Table -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-table me-2"></i>Metric Values
|
|
</h5>
|
|
<div class="card-tools">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="exportData('pdf')">
|
|
<i class="fas fa-file-pdf me-1"></i>PDF
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="exportData('excel')">
|
|
<i class="fas fa-file-excel me-1"></i>Excel
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.print()">
|
|
<i class="fas fa-print me-1"></i>Print
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if metric_values %}
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<a href="?{% url_replace request 'sort' 'metric__name' %}">
|
|
Metric Name
|
|
{% if request.GET.sort == 'metric__name' %}
|
|
<i class="fas fa-sort-up"></i>
|
|
{% elif request.GET.sort == '-metric__name' %}
|
|
<i class="fas fa-sort-down"></i>
|
|
{% else %}
|
|
<i class="fas fa-sort"></i>
|
|
{% endif %}
|
|
</a>
|
|
</th>
|
|
<th>
|
|
<a href="?{% url_replace request 'sort' 'value' %}">
|
|
Value
|
|
{% if request.GET.sort == 'value' %}
|
|
<i class="fas fa-sort-up"></i>
|
|
{% elif request.GET.sort == '-value' %}
|
|
<i class="fas fa-sort-down"></i>
|
|
{% else %}
|
|
<i class="fas fa-sort"></i>
|
|
{% endif %}
|
|
</a>
|
|
</th>
|
|
<th>Unit</th>
|
|
<th>
|
|
<a href="?{% url_replace request 'sort' 'calculated_at' %}">
|
|
Calculated At
|
|
{% if request.GET.sort == 'calculated_at' %}
|
|
<i class="fas fa-sort-up"></i>
|
|
{% elif request.GET.sort == '-calculated_at' %}
|
|
<i class="fas fa-sort-down"></i>
|
|
{% else %}
|
|
<i class="fas fa-sort"></i>
|
|
{% endif %}
|
|
</a>
|
|
</th>
|
|
<th>Status</th>
|
|
<th>Category</th>
|
|
<th>Duration</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for value in metric_values %}
|
|
<tr>
|
|
<td>
|
|
<div class="metric-info">
|
|
<strong>{{ value.metric.name }}</strong>
|
|
{% if value.metric.description %}
|
|
<br><small class="text-muted">{{ value.metric.description|truncatechars:50 }}</small>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="value-display">
|
|
<strong class="text-primary">{{ value.formatted_value }}</strong>
|
|
{% if value.previous_value %}
|
|
<br><small class="text-muted">
|
|
{% if value.change_percentage %}
|
|
<span class="change-indicator {% if value.change_percentage > 0 %}text-success{% else %}text-danger{% endif %}">
|
|
<i class="fas fa-arrow-{% if value.change_percentage > 0 %}up{% else %}down{% endif %} me-1"></i>
|
|
{{ value.change_percentage|floatformat:1 }}%
|
|
</span>
|
|
{% endif %}
|
|
</small>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td>{{ value.metric.unit|default:"N/A" }}</td>
|
|
<td>
|
|
<div class="datetime-info">
|
|
{{ value.calculated_at|date:"M d, Y" }}
|
|
<br><small class="text-muted">{{ value.calculated_at|time:"H:i" }}</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{% if value.status == 'success' %}
|
|
<span class="badge bg-success">Success</span>
|
|
{% elif value.status == 'error' %}
|
|
<span class="badge bg-danger">Error</span>
|
|
{% elif value.status == 'pending' %}
|
|
<span class="badge bg-warning">Pending</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ value.status|title }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-primary">{{ value.metric.get_category_display }}</span>
|
|
</td>
|
|
<td>
|
|
{% if value.calculation_duration %}
|
|
{{ value.calculation_duration }}
|
|
{% else %}
|
|
<span class="text-muted">N/A</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-primary btn-sm"
|
|
onclick="viewDetails('{{ value.id }}')" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
{% if value.status == 'error' %}
|
|
<button type="button" class="btn btn-outline-warning btn-sm"
|
|
onclick="retryCalculation('{{ value.id }}')" title="Retry Calculation">
|
|
<i class="fas fa-redo"></i>
|
|
</button>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-outline-info btn-sm"
|
|
onclick="viewTrend('{{ value.metric.id }}')" title="View Trend">
|
|
<i class="fas fa-chart-line"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm"
|
|
onclick="exportValue('{{ value.id }}')" title="Export">
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
<div class="pagination-info">
|
|
<span class="text-muted">
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} entries
|
|
</span>
|
|
</div>
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination pagination-sm mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% url_replace request 'page' page_obj.previous_page_number %}">
|
|
<i class="fas fa-chevron-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="?{% url_replace request 'page' num %}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% url_replace request 'page' page_obj.next_page_number %}">
|
|
<i class="fas fa-chevron-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-chart-line fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No Metric Values Found</h5>
|
|
<p class="text-muted">No metric values match your current filters. Try adjusting your search criteria or calculate new metrics.</p>
|
|
<a href="{% url 'analytics:calculate_metric' %}" class="btn btn-primary">
|
|
<i class="fas fa-calculator me-1"></i>Calculate Metrics
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Value Details Modal -->
|
|
<div class="modal fade" id="valueDetailsModal" tabindex="-1" aria-labelledby="valueDetailsModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="valueDetailsModalLabel">
|
|
<i class="fas fa-chart-line me-2"></i>Metric Value Details
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="valueDetailsContent">
|
|
<!-- Content will be loaded here -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function clearFilters() {
|
|
window.location.href = window.location.pathname;
|
|
}
|
|
|
|
function exportData(format) {
|
|
const params = new URLSearchParams(window.location.search);
|
|
params.set('export', format);
|
|
window.location.href = '?' + params.toString();
|
|
}
|
|
|
|
function viewDetails(valueId) {
|
|
// Show loading state
|
|
document.getElementById('valueDetailsContent').innerHTML = `
|
|
<div class="text-center py-4">
|
|
<i class="fas fa-spinner fa-spin fa-2x text-primary mb-3"></i>
|
|
<p>Loading metric value details...</p>
|
|
</div>
|
|
`;
|
|
|
|
// Show modal
|
|
const modal = new bootstrap.Modal(document.getElementById('valueDetailsModal'));
|
|
modal.show();
|
|
|
|
// Simulate loading details
|
|
setTimeout(() => {
|
|
document.getElementById('valueDetailsContent').innerHTML = `
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Calculation Details</h6>
|
|
<table class="table table-sm">
|
|
<tr><td><strong>Value ID:</strong></td><td>${valueId}</td></tr>
|
|
<tr><td><strong>Calculation Method:</strong></td><td>Formula-based</td></tr>
|
|
<tr><td><strong>Data Points:</strong></td><td>1,234</td></tr>
|
|
<tr><td><strong>Execution Time:</strong></td><td>0.15 seconds</td></tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Parameters Used</h6>
|
|
<table class="table table-sm">
|
|
<tr><td><strong>Date Range:</strong></td><td>Last 30 days</td></tr>
|
|
<tr><td><strong>Department:</strong></td><td>All</td></tr>
|
|
<tr><td><strong>Patient Type:</strong></td><td>All</td></tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}, 1000);
|
|
}
|
|
|
|
function retryCalculation(valueId) {
|
|
if (confirm('Retry calculation for this metric value?')) {
|
|
alert('Calculation retry initiated. You will be notified when complete.');
|
|
}
|
|
}
|
|
|
|
function viewTrend(metricId) {
|
|
window.location.href = `/analytics/metrics/${metricId}/trend/`;
|
|
}
|
|
|
|
function exportValue(valueId) {
|
|
alert('Exporting metric value data...');
|
|
}
|
|
|
|
// Auto-submit form on filter change
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const filterInputs = document.querySelectorAll('#filterForm select, #filterForm input');
|
|
filterInputs.forEach(input => {
|
|
input.addEventListener('change', function() {
|
|
if (this.type !== 'date' || (this.value && this.value.trim() !== '')) {
|
|
document.getElementById('filterForm').submit();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.metric-info strong {
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.value-display strong {
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.change-indicator {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.datetime-info {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
margin-right: 2px;
|
|
}
|
|
|
|
.btn-group .btn:last-child {
|
|
margin-right: 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.page-btn {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.card-tools {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.btn-group {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
margin-right: 0;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.table-responsive {
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.pagination-info {
|
|
margin-bottom: 15px;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|