592 lines
29 KiB
HTML
592 lines
29 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Metric Definitions - 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 Definitions</h4>
|
|
<h6>Manage and configure analytics metrics</h6>
|
|
</div>
|
|
<div class="page-btn">
|
|
<a href="{% url 'analytics:metric_definition_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Add Metric
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row">
|
|
<div class="col-lg-3 col-sm-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-primary border-primary">
|
|
<i class="fas fa-chart-line"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ total_metrics|default:"0" }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">Total Metrics</h6>
|
|
<div class="progress progress-sm">
|
|
<div class="progress-bar bg-primary" style="width: 100%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-success border-success">
|
|
<i class="fas fa-check-circle"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ active_metrics|default:"0" }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">Active Metrics</h6>
|
|
<div class="progress progress-sm">
|
|
<div class="progress-bar bg-success" style="width: {{ active_percentage|default:0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-warning border-warning">
|
|
<i class="fas fa-calculator"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ calculated_today|default:"0" }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">Calculated Today</h6>
|
|
<div class="progress progress-sm">
|
|
<div class="progress-bar bg-warning" style="width: 75%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-info border-info">
|
|
<i class="fas fa-database"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ data_sources_used|default:"0" }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">Data Sources</h6>
|
|
<div class="progress progress-sm">
|
|
<div class="progress-bar bg-info" style="width: 60%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters and Search -->
|
|
<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="search" class="form-label">Search</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="search" name="search"
|
|
value="{{ request.GET.search }}" placeholder="Search metrics...">
|
|
<button class="btn btn-outline-secondary" type="submit">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</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>
|
|
<option value="patient_satisfaction" {% if request.GET.category == 'patient_satisfaction' %}selected{% endif %}>Patient Satisfaction</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<label for="metric_type" class="form-label">Type</label>
|
|
<select class="form-select" id="metric_type" name="metric_type">
|
|
<option value="">All Types</option>
|
|
<option value="count" {% if request.GET.metric_type == 'count' %}selected{% endif %}>Count</option>
|
|
<option value="percentage" {% if request.GET.metric_type == 'percentage' %}selected{% endif %}>Percentage</option>
|
|
<option value="average" {% if request.GET.metric_type == 'average' %}selected{% endif %}>Average</option>
|
|
<option value="sum" {% if request.GET.metric_type == 'sum' %}selected{% endif %}>Sum</option>
|
|
<option value="ratio" {% if request.GET.metric_type == 'ratio' %}selected{% endif %}>Ratio</option>
|
|
</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="active" {% if request.GET.status == 'active' %}selected{% endif %}>Active</option>
|
|
<option value="inactive" {% if request.GET.status == 'inactive' %}selected{% endif %}>Inactive</option>
|
|
<option value="draft" {% if request.GET.status == 'draft' %}selected{% endif %}>Draft</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="data_source" class="form-label">Data Source</label>
|
|
<select class="form-select" id="data_source" name="data_source">
|
|
<option value="">All Sources</option>
|
|
{% for source in data_sources %}
|
|
<option value="{{ source.id }}" {% if request.GET.data_source == source.id|stringformat:"s" %}selected{% endif %}>
|
|
{{ source.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<button type="submit" class="btn btn-primary me-2">
|
|
<i class="fas fa-filter me-1"></i>Apply Filters
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary me-2" onclick="exportMetrics()">
|
|
<i class="fas fa-download me-1"></i>Export
|
|
</button>
|
|
<button type="button" class="btn btn-outline-info" onclick="bulkActions()">
|
|
<i class="fas fa-tasks me-1"></i>Bulk Actions
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metrics List -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-list me-2"></i>Metric Definitions
|
|
</h5>
|
|
<div class="card-tools">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm active" onclick="toggleView('list')">
|
|
<i class="fas fa-list"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="toggleView('grid')">
|
|
<i class="fas fa-th"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- List View -->
|
|
<div id="listView" class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<input type="checkbox" id="selectAll" onchange="toggleSelectAll()">
|
|
</th>
|
|
<th>Metric Name</th>
|
|
<th>Category</th>
|
|
<th>Type</th>
|
|
<th>Data Source</th>
|
|
<th>Status</th>
|
|
<th>Last Calculated</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for metric in metrics %}
|
|
<tr>
|
|
<td>
|
|
<input type="checkbox" class="metric-checkbox" value="{{ metric.id }}">
|
|
</td>
|
|
<td>
|
|
<div class="metric-info">
|
|
<h6 class="mb-1">{{ metric.name }}</h6>
|
|
<p class="text-muted mb-0 small">{{ metric.description|truncatechars:50 }}</p>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-primary">{{ metric.get_category_display }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ metric.get_metric_type_display }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="text-muted">{{ metric.data_source.name|default:"N/A" }}</span>
|
|
</td>
|
|
<td>
|
|
{% if metric.status == 'active' %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% elif metric.status == 'inactive' %}
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
{% else %}
|
|
<span class="badge bg-warning">Draft</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if metric.last_calculated %}
|
|
<span class="text-muted">{{ metric.last_calculated|timesince }} ago</span>
|
|
{% else %}
|
|
<span class="text-muted">Never</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="dropdown">
|
|
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button"
|
|
data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'analytics:metric_definition_detail' metric.pk %}">
|
|
<i class="fas fa-eye me-2"></i>View
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'analytics:metric_definition_update' metric.pk %}">
|
|
<i class="fas fa-edit me-2"></i>Edit
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="calculateMetric('{{ metric.id }}')">
|
|
<i class="fas fa-calculator me-2"></i>Calculate
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="duplicateMetric('{{ metric.id }}')">
|
|
<i class="fas fa-copy me-2"></i>Duplicate
|
|
</a>
|
|
</li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li>
|
|
<a class="dropdown-item text-danger" href="{% url 'analytics:metric_definition_delete' metric.pk %}">
|
|
<i class="fas fa-trash me-2"></i>Delete
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="8" class="text-center py-4">
|
|
<i class="fas fa-chart-line fa-3x text-muted mb-3"></i>
|
|
<h6 class="text-muted">No Metrics Found</h6>
|
|
<p class="text-muted">Create your first metric definition to get started.</p>
|
|
<a href="{% url 'analytics:metric_definition_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Create Metric
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Grid View -->
|
|
<div id="gridView" class="row" style="display: none;">
|
|
{% for metric in metrics %}
|
|
<div class="col-lg-4 col-md-6 mb-4">
|
|
<div class="card metric-card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<div class="metric-status">
|
|
{% if metric.status == 'active' %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% elif metric.status == 'inactive' %}
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
{% else %}
|
|
<span class="badge bg-warning">Draft</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="dropdown">
|
|
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button"
|
|
data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'analytics:metric_definition_detail' metric.pk %}">
|
|
<i class="fas fa-eye me-2"></i>View
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'analytics:metric_definition_update' metric.pk %}">
|
|
<i class="fas fa-edit me-2"></i>Edit
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="calculateMetric('{{ metric.id }}')">
|
|
<i class="fas fa-calculator me-2"></i>Calculate
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<h6 class="card-title">{{ metric.name }}</h6>
|
|
<p class="card-text text-muted small">{{ metric.description|truncatechars:80 }}</p>
|
|
|
|
<div class="metric-details">
|
|
<div class="detail-row">
|
|
<span class="detail-label">Category:</span>
|
|
<span class="badge bg-primary">{{ metric.get_category_display }}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Type:</span>
|
|
<span class="badge bg-info">{{ metric.get_metric_type_display }}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Source:</span>
|
|
<span class="text-muted">{{ metric.data_source.name|default:"N/A" }}</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Last Calculated:</span>
|
|
<span class="text-muted">
|
|
{% if metric.last_calculated %}
|
|
{{ metric.last_calculated|timesince }} ago
|
|
{% else %}
|
|
Never
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="d-flex justify-content-between">
|
|
<a href="{% url 'analytics:metric_definition_detail' metric.pk %}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-eye me-1"></i>View
|
|
</a>
|
|
<button type="button" class="btn btn-outline-success btn-sm" onclick="calculateMetric('{{ metric.id }}')">
|
|
<i class="fas fa-calculator me-1"></i>Calculate
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-chart-line fa-3x text-muted mb-3"></i>
|
|
<h6 class="text-muted">No Metrics Found</h6>
|
|
<p class="text-muted">Create your first metric definition to get started.</p>
|
|
<a href="{% url 'analytics:metric_definition_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Create Metric
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<nav aria-label="Metrics pagination">
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
|
</li>
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Last</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function toggleView(viewType) {
|
|
const listView = document.getElementById('listView');
|
|
const gridView = document.getElementById('gridView');
|
|
const buttons = document.querySelectorAll('.btn-group button');
|
|
|
|
buttons.forEach(btn => btn.classList.remove('active'));
|
|
|
|
if (viewType === 'list') {
|
|
listView.style.display = 'block';
|
|
gridView.style.display = 'none';
|
|
buttons[0].classList.add('active');
|
|
} else {
|
|
listView.style.display = 'none';
|
|
gridView.style.display = 'block';
|
|
buttons[1].classList.add('active');
|
|
}
|
|
}
|
|
|
|
function toggleSelectAll() {
|
|
const selectAll = document.getElementById('selectAll');
|
|
const checkboxes = document.querySelectorAll('.metric-checkbox');
|
|
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = selectAll.checked;
|
|
});
|
|
}
|
|
|
|
function clearFilters() {
|
|
document.getElementById('filterForm').reset();
|
|
window.location.href = window.location.pathname;
|
|
}
|
|
|
|
function exportMetrics() {
|
|
const selectedMetrics = Array.from(document.querySelectorAll('.metric-checkbox:checked'))
|
|
.map(cb => cb.value);
|
|
|
|
if (selectedMetrics.length === 0) {
|
|
alert('Please select metrics to export');
|
|
return;
|
|
}
|
|
|
|
alert(`Exporting ${selectedMetrics.length} metric(s)...`);
|
|
}
|
|
|
|
function bulkActions() {
|
|
const selectedMetrics = Array.from(document.querySelectorAll('.metric-checkbox:checked'))
|
|
.map(cb => cb.value);
|
|
|
|
if (selectedMetrics.length === 0) {
|
|
alert('Please select metrics for bulk actions');
|
|
return;
|
|
}
|
|
|
|
const action = prompt('Choose action:\n1. Activate\n2. Deactivate\n3. Delete\n\nEnter number:');
|
|
|
|
if (action) {
|
|
alert(`Performing bulk action on ${selectedMetrics.length} metric(s)...`);
|
|
}
|
|
}
|
|
|
|
function calculateMetric(metricId) {
|
|
if (confirm('Calculate this metric now?')) {
|
|
alert('Metric calculation started...');
|
|
}
|
|
}
|
|
|
|
function duplicateMetric(metricId) {
|
|
if (confirm('Create a copy of this metric?')) {
|
|
alert('Metric duplicated successfully!');
|
|
}
|
|
}
|
|
|
|
// Auto-submit form on filter change
|
|
document.querySelectorAll('#filterForm select').forEach(select => {
|
|
select.addEventListener('change', function() {
|
|
document.getElementById('filterForm').submit();
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.metric-card {
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.metric-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.metric-details {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.detail-label {
|
|
font-size: 0.875rem;
|
|
color: #6c757d;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.metric-info h6 {
|
|
margin-bottom: 5px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.page-btn {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.card-tools {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.metric-card {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.detail-row {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 5px;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|