569 lines
24 KiB
HTML
569 lines
24 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Metrics - Analytics{% endblock %}
|
|
|
|
{% block css %}
|
|
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- BEGIN breadcrumb -->
|
|
<ol class="breadcrumb float-xl-end">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'analytics:dashboard' %}">Analytics</a></li>
|
|
<li class="breadcrumb-item active">Metrics</li>
|
|
</ol>
|
|
<!-- END breadcrumb -->
|
|
|
|
<!-- BEGIN page-header -->
|
|
<h1 class="page-header">
|
|
Analytics Metrics
|
|
<small>Key Performance Indicators</small>
|
|
</h1>
|
|
<!-- END page-header -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Metric Management</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="{% url 'analytics:metric_definition_create' %}" class="btn btn-xs btn-success me-2">
|
|
<i class="fa fa-plus"></i> Create Metric
|
|
</a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<!-- Filters and Search -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label">Search Metrics</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="search-input" placeholder="Metric name, description...">
|
|
<button class="btn btn-outline-secondary" type="button" id="search-btn">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Category</label>
|
|
<select class="form-select" id="category-filter">
|
|
<option value="">All Categories</option>
|
|
<option value="FINANCIAL">Financial</option>
|
|
<option value="CLINICAL">Clinical</option>
|
|
<option value="OPERATIONAL">Operational</option>
|
|
<option value="QUALITY">Quality</option>
|
|
<option value="PATIENT_SATISFACTION">Patient Satisfaction</option>
|
|
<option value="STAFF_PERFORMANCE">Staff Performance</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" id="status-filter">
|
|
<option value="">All Status</option>
|
|
<option value="ACTIVE">Active</option>
|
|
<option value="INACTIVE">Inactive</option>
|
|
<option value="DRAFT">Draft</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label">Frequency</label>
|
|
<select class="form-select" id="frequency-filter">
|
|
<option value="">All Frequencies</option>
|
|
<option value="REAL_TIME">Real Time</option>
|
|
<option value="HOURLY">Hourly</option>
|
|
<option value="DAILY">Daily</option>
|
|
<option value="WEEKLY">Weekly</option>
|
|
<option value="MONTHLY">Monthly</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Stats -->
|
|
<div class="row mb-4">
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 bg-primary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-fill">
|
|
<div class="fs-10px text-white-transparent-5 mb-1">TOTAL METRICS</div>
|
|
<div class="fs-18px fw-900 text-white" id="total-metrics">-</div>
|
|
</div>
|
|
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
|
<i class="fa fa-chart-line fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 bg-success text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-fill">
|
|
<div class="fs-10px text-white-transparent-5 mb-1">ACTIVE METRICS</div>
|
|
<div class="fs-18px fw-900 text-white" id="active-metrics">-</div>
|
|
</div>
|
|
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
|
<i class="fa fa-play fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 bg-warning text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-fill">
|
|
<div class="fs-10px text-white-transparent-5 mb-1">ALERTS TRIGGERED</div>
|
|
<div class="fs-18px fw-900 text-white" id="alerts-triggered">-</div>
|
|
</div>
|
|
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
|
<i class="fa fa-exclamation-triangle fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 bg-info text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-fill">
|
|
<div class="fs-10px text-white-transparent-5 mb-1">UPDATED TODAY</div>
|
|
<div class="fs-18px fw-900 text-white" id="updated-today">-</div>
|
|
</div>
|
|
<div class="w-50px h-50px bg-white-transparent-2 rounded-circle d-flex align-items-center justify-content-center">
|
|
<i class="fa fa-sync fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary" id="bulk-actions-btn" disabled>
|
|
<i class="fa fa-tasks me-2"></i>Bulk Actions
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split"
|
|
data-bs-toggle="dropdown" id="bulk-dropdown" disabled>
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="bulkActivate()">
|
|
<i class="fa fa-play me-2"></i>Activate Selected
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkDeactivate()">
|
|
<i class="fa fa-pause me-2"></i>Deactivate Selected
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkRefresh()">
|
|
<i class="fa fa-sync me-2"></i>Refresh Selected
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkExport()">
|
|
<i class="fa fa-download me-2"></i>Export Selected
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshTable()">
|
|
<i class="fa fa-refresh me-2"></i>Refresh
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fa fa-download me-2"></i>Export
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="exportData('csv')">
|
|
<i class="fa fa-file-csv me-2"></i>Export as CSV
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportData('excel')">
|
|
<i class="fa fa-file-excel me-2"></i>Export as Excel
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metrics Table -->
|
|
<div class="table-responsive">
|
|
<table id="metrics-table" class="table table-striped table-bordered align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th width="30">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="select-all">
|
|
</div>
|
|
</th>
|
|
<th>Metric Name</th>
|
|
<th>Category</th>
|
|
<th>Current Value</th>
|
|
<th>Target</th>
|
|
<th>Status</th>
|
|
<th>Last Updated</th>
|
|
<th>Frequency</th>
|
|
<th width="120">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Data will be loaded via AJAX -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
|
|
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
|
|
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
|
|
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
var table;
|
|
|
|
// Initialize DataTable
|
|
table = $('#metrics-table').DataTable({
|
|
processing: true,
|
|
serverSide: true,
|
|
ajax: {
|
|
url: '{% url "analytics:metric_list" %}',
|
|
data: function(d) {
|
|
d.search_value = $('#search-input').val();
|
|
d.category = $('#category-filter').val();
|
|
d.status = $('#status-filter').val();
|
|
d.frequency = $('#frequency-filter').val();
|
|
}
|
|
},
|
|
columns: [
|
|
{
|
|
data: 'id',
|
|
orderable: false,
|
|
render: function(data, type, row) {
|
|
return '<div class="form-check"><input class="form-check-input metric-checkbox" type="checkbox" value="' + data + '"></div>';
|
|
}
|
|
},
|
|
{
|
|
data: 'name',
|
|
render: function(data, type, row) {
|
|
return '<div>' +
|
|
'<div class="fw-bold">' + data + '</div>' +
|
|
(row.description ? '<div class="text-muted small">' + row.description + '</div>' : '') +
|
|
'</div>';
|
|
}
|
|
},
|
|
{
|
|
data: 'category',
|
|
render: function(data, type, row) {
|
|
var colors = {
|
|
'FINANCIAL': 'success',
|
|
'CLINICAL': 'primary',
|
|
'OPERATIONAL': 'info',
|
|
'QUALITY': 'warning',
|
|
'PATIENT_SATISFACTION': 'purple',
|
|
'STAFF_PERFORMANCE': 'secondary'
|
|
};
|
|
return '<span class="badge bg-' + (colors[data] || 'secondary') + '">' + row.category_display + '</span>';
|
|
}
|
|
},
|
|
{
|
|
data: 'current_value',
|
|
render: function(data, type, row) {
|
|
if (data !== null) {
|
|
var value = parseFloat(data);
|
|
var formatted = row.unit ? value + ' ' + row.unit : value.toString();
|
|
|
|
// Color based on target comparison
|
|
var colorClass = 'text-dark';
|
|
if (row.target_value !== null) {
|
|
var target = parseFloat(row.target_value);
|
|
if (row.target_direction === 'HIGHER') {
|
|
colorClass = value >= target ? 'text-success' : 'text-danger';
|
|
} else if (row.target_direction === 'LOWER') {
|
|
colorClass = value <= target ? 'text-success' : 'text-danger';
|
|
}
|
|
}
|
|
|
|
return '<span class="fw-bold ' + colorClass + '">' + formatted + '</span>';
|
|
} else {
|
|
return '<span class="text-muted">No data</span>';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
data: 'target_value',
|
|
render: function(data, type, row) {
|
|
if (data !== null) {
|
|
var value = parseFloat(data);
|
|
var formatted = row.unit ? value + ' ' + row.unit : value.toString();
|
|
var direction = row.target_direction === 'HIGHER' ? '↑' : '↓';
|
|
return '<span class="text-info">' + direction + ' ' + formatted + '</span>';
|
|
} else {
|
|
return '<span class="text-muted">No target</span>';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
data: 'status',
|
|
render: function(data, type, row) {
|
|
var colors = {
|
|
'ACTIVE': 'success',
|
|
'INACTIVE': 'secondary',
|
|
'DRAFT': 'warning'
|
|
};
|
|
return '<span class="badge bg-' + (colors[data] || 'secondary') + '">' + row.status_display + '</span>';
|
|
}
|
|
},
|
|
{
|
|
data: 'last_updated',
|
|
render: function(data, type, row) {
|
|
if (data) {
|
|
return moment(data).format('MMM DD, YYYY HH:mm');
|
|
} else {
|
|
return '<span class="text-muted">Never</span>';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
data: 'update_frequency',
|
|
render: function(data, type, row) {
|
|
var icons = {
|
|
'REAL_TIME': 'bolt',
|
|
'HOURLY': 'clock',
|
|
'DAILY': 'calendar-day',
|
|
'WEEKLY': 'calendar-week',
|
|
'MONTHLY': 'calendar-alt'
|
|
};
|
|
return '<i class="fa fa-' + (icons[data] || 'clock') + ' me-2"></i>' + row.frequency_display;
|
|
}
|
|
},
|
|
{
|
|
data: 'id',
|
|
orderable: false,
|
|
render: function(data, type, row) {
|
|
var actions = '<div class="btn-group btn-group-sm">';
|
|
actions += '<button class="btn btn-outline-primary" onclick="viewMetric(' + data + ')" title="View">';
|
|
actions += '<i class="fa fa-eye"></i></button>';
|
|
actions += '<button class="btn btn-outline-success" onclick="refreshMetric(' + data + ')" title="Refresh">';
|
|
actions += '<i class="fa fa-sync"></i></button>';
|
|
actions += '<button class="btn btn-outline-secondary" onclick="editMetric(' + data + ')" title="Edit">';
|
|
actions += '<i class="fa fa-edit"></i></button>';
|
|
actions += '</div>';
|
|
return actions;
|
|
}
|
|
}
|
|
],
|
|
order: [[6, 'desc']],
|
|
pageLength: 25,
|
|
responsive: true,
|
|
language: {
|
|
processing: '<div class="d-flex justify-content-center"><div class="spinner-border" role="status"></div></div>'
|
|
}
|
|
});
|
|
|
|
// Load stats
|
|
loadStats();
|
|
|
|
// Filter event handlers
|
|
$('#search-input, #category-filter, #status-filter, #frequency-filter').on('change keyup', function() {
|
|
table.draw();
|
|
});
|
|
|
|
// Select all checkbox
|
|
$('#select-all').on('change', function() {
|
|
$('.metric-checkbox').prop('checked', this.checked);
|
|
updateBulkActions();
|
|
});
|
|
|
|
// Individual checkbox change
|
|
$(document).on('change', '.metric-checkbox', function() {
|
|
updateBulkActions();
|
|
});
|
|
|
|
// Auto-refresh every 2 minutes
|
|
setInterval(function() {
|
|
table.ajax.reload(null, false);
|
|
loadStats();
|
|
}, 120000);
|
|
});
|
|
|
|
function loadStats() {
|
|
$.ajax({
|
|
url: '{% url "analytics:metric_stats" %}',
|
|
success: function(data) {
|
|
$('#total-metrics').text(data.total_metrics);
|
|
$('#active-metrics').text(data.active_metrics);
|
|
$('#alerts-triggered').text(data.alerts_triggered);
|
|
$('#updated-today').text(data.updated_today);
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateBulkActions() {
|
|
var checkedCount = $('.metric-checkbox:checked').length;
|
|
$('#bulk-actions-btn, #bulk-dropdown').prop('disabled', checkedCount === 0);
|
|
}
|
|
|
|
function viewMetric(metricId) {
|
|
window.location.href = '{% url "analytics:metric_definition_detail" 0 %}'.replace('0', metricId);
|
|
}
|
|
|
|
function editMetric(metricId) {
|
|
window.location.href = '{% url "analytics:metric_definition_update" 0 %}'.replace('0', metricId);
|
|
}
|
|
|
|
{#function refreshMetric(metricId) {#}
|
|
{# $.ajax({#}
|
|
{# url: '{% url "analytics:metric_refresh" 0 %}'.replace('0', metricId),#}
|
|
{# method: 'POST',#}
|
|
{# data: {#}
|
|
{# 'csrfmiddlewaretoken': '{{ csrf_token }}'#}
|
|
{# },#}
|
|
{# success: function(response) {#}
|
|
{# if (response.success) {#}
|
|
{# toastr.success('Metric refreshed successfully');#}
|
|
{# $('#metrics-table').DataTable().ajax.reload();#}
|
|
{# } else {#}
|
|
{# toastr.error('Failed to refresh metric');#}
|
|
{# }#}
|
|
{# },#}
|
|
{# error: function() {#}
|
|
{# toastr.error('An error occurred while refreshing the metric');#}
|
|
{# }#}
|
|
{# });#}
|
|
{# }#}
|
|
|
|
{#function bulkActivate() {#}
|
|
{# var selectedIds = $('.metric-checkbox:checked').map(function() {#}
|
|
{# return this.value;#}
|
|
{# }).get();#}
|
|
{# #}
|
|
{# if (selectedIds.length === 0) {#}
|
|
{# toastr.warning('Please select metrics to activate');#}
|
|
{# return;#}
|
|
{# }#}
|
|
{# #}
|
|
{# $.ajax({#}
|
|
{# url: '{% url "analytics:metric_bulk_activate" %}',#}
|
|
{# method: 'POST',#}
|
|
{# data: {#}
|
|
{# 'metric_ids': selectedIds,#}
|
|
{# 'csrfmiddlewaretoken': '{{ csrf_token }}'#}
|
|
{# },#}
|
|
{# success: function(response) {#}
|
|
{# if (response.success) {#}
|
|
{# toastr.success(response.message);#}
|
|
{# $('#metrics-table').DataTable().ajax.reload();#}
|
|
{# $('#select-all').prop('checked', false);#}
|
|
{# updateBulkActions();#}
|
|
{# } else {#}
|
|
{# toastr.error('Failed to activate metrics');#}
|
|
{# }#}
|
|
{# },#}
|
|
{# error: function() {#}
|
|
{# toastr.error('An error occurred during bulk activation');#}
|
|
{# }#}
|
|
{# });#}
|
|
{# }#}
|
|
{##}
|
|
{#function bulkDeactivate() {#}
|
|
{# var selectedIds = $('.metric-checkbox:checked').map(function() {#}
|
|
{# return this.value;#}
|
|
{# }).get();#}
|
|
{# #}
|
|
{# if (selectedIds.length === 0) {#}
|
|
{# toastr.warning('Please select metrics to deactivate');#}
|
|
{# return;#}
|
|
{# }#}
|
|
{# #}
|
|
{# $.ajax({#}
|
|
{# url: '{% url "analytics:metric_bulk_deactivate" %}',#}
|
|
{# method: 'POST',#}
|
|
{# data: {#}
|
|
{# 'metric_ids': selectedIds,#}
|
|
{# 'csrfmiddlewaretoken': '{{ csrf_token }}'#}
|
|
{# },#}
|
|
{# success: function(response) {#}
|
|
{# if (response.success) {#}
|
|
{# toastr.success(response.message);#}
|
|
{# $('#metrics-table').DataTable().ajax.reload();#}
|
|
{# $('#select-all').prop('checked', false);#}
|
|
{# updateBulkActions();#}
|
|
{# } else {#}
|
|
{# toastr.error('Failed to deactivate metrics');#}
|
|
{# }#}
|
|
{# },#}
|
|
{# error: function() {#}
|
|
{# toastr.error('An error occurred during bulk deactivation');#}
|
|
{# }#}
|
|
{# });#}
|
|
{# }#}
|
|
{##}
|
|
{#function bulkRefresh() {#}
|
|
{# var selectedIds = $('.metric-checkbox:checked').map(function() {#}
|
|
{# return this.value;#}
|
|
{# }).get();#}
|
|
{# #}
|
|
{# if (selectedIds.length === 0) {#}
|
|
{# toastr.warning('Please select metrics to refresh');#}
|
|
{# return;#}
|
|
{# }#}
|
|
{# #}
|
|
{# $.ajax({#}
|
|
{# url: '{% url "analytics:metric_bulk_refresh" %}',#}
|
|
{# method: 'POST',#}
|
|
{# data: {#}
|
|
{# 'metric_ids': selectedIds,#}
|
|
{# 'csrfmiddlewaretoken': '{{ csrf_token }}'#}
|
|
{# },#}
|
|
{# success: function(response) {#}
|
|
{# if (response.success) {#}
|
|
{# toastr.success(response.message);#}
|
|
{# $('#metrics-table').DataTable().ajax.reload();#}
|
|
{# $('#select-all').prop('checked', false);#}
|
|
{# updateBulkActions();#}
|
|
{# } else {#}
|
|
{# toastr.error('Failed to refresh metrics');#}
|
|
{# }#}
|
|
{# },#}
|
|
{# error: function() {#}
|
|
{# toastr.error('An error occurred during bulk refresh');#}
|
|
{# }#}
|
|
{# });#}
|
|
{# }#}
|
|
{##}
|
|
{#function bulkExport() {#}
|
|
{# var selectedIds = $('.metric-checkbox:checked').map(function() {#}
|
|
{# return this.value;#}
|
|
{# }).get();#}
|
|
{# #}
|
|
{# if (selectedIds.length === 0) {#}
|
|
{# toastr.warning('Please select metrics to export');#}
|
|
{# return;#}
|
|
{# }#}
|
|
{# #}
|
|
{# window.location.href = '{% url "analytics:metric_export" %}?ids=' + selectedIds.join(',');#}
|
|
{# }#}
|
|
{##}
|
|
{#function exportData(format) {#}
|
|
{# window.location.href = '{% url "analytics:metric_export" %}?format=' + format;#}
|
|
{# }#}
|
|
|
|
function refreshTable() {
|
|
$('#metrics-table').DataTable().ajax.reload();
|
|
loadStats();
|
|
toastr.info('Table refreshed');
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|